lmi-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[lmi-commits] [lmi] master 60b29957 4/4: Move semantics, nascent


From: Greg Chicares
Subject: [lmi-commits] [lmi] master 60b29957 4/4: Move semantics, nascent
Date: Wed, 10 Aug 2022 10:53:48 -0400 (EDT)

branch: master
commit 60b29957a315540e9c7ad8b9b0c5cca4f5f8b139
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>

    Move semantics, nascent
    
    This commit subsumes the soon-to-be-deleted odd/move_semantics branch.
    
    It is conventional to place a header's implementation details in
    namespace "detail", in order to mark them as notionally private. Here,
    namespace "smf_mechanics" is used for the same purpose. For headers
    that do not enclose all their contents in a namespace such as "lmi",
    this seems preferable, to guard against collision of "details" in
    different headers.
---
 smf.hpp      |  46 ++++++++++
 smf_test.cpp | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 333 insertions(+), 3 deletions(-)

diff --git a/smf.hpp b/smf.hpp
index a3282fe8..531fd422 100644
--- a/smf.hpp
+++ b/smf.hpp
@@ -24,4 +24,50 @@
 
 #include "config.hpp"
 
+#include <type_traits>
+
+namespace smf_mechanics
+{
+/// Induce ambiguity between a class's copy and move SMFs.
+///
+/// If class T has both a copy and a move ctor, both of which can be
+/// considered in overload resolution, then instantiating this:
+///   T t {ambiguator<T>{}};
+/// would be an error because neither ctor is better than the other.
+/// However, detecting that ambiguity in this way:
+///   !std::is_constructible_v<T, ambiguator<T>>
+/// is not an error. Similarly, this:
+///   !std::is_assignable_v   <T, ambiguator<T>>
+/// detects equiplausible assignment without inducing an error.
+///
+/// Those non-erroneous expressions happen to do the right thing even
+/// if class T is an aggregate, for which instantiating this:
+///   T t {ambiguator<T>{}};
+/// would be an error for a different reason. See:
+///   https://lists.nongnu.org/archive/html/lmi/2022-08/msg00005.html
+
+template<typename T>
+struct ambiguator
+{
+    operator T const&();
+    operator T&&();
+};
+
+template<typename T> concept equiplausibly_constructible =
+    !std::is_constructible_v<T,ambiguator<T>>;
+
+template<typename T> concept equiplausibly_assignable =
+    !std::is_assignable_v<T,ambiguator<T>>;
+} // namespace smf_mechanics
+
+template<typename T> concept well_move_constructible =
+       std::is_move_constructible_v<T>
+    && smf_mechanics::equiplausibly_constructible<T>
+    ;
+
+template<typename T> concept well_move_assignable =
+       std::is_move_assignable_v<T>
+    && smf_mechanics::equiplausibly_assignable<T>
+    ;
+
 #endif // smf_hpp
diff --git a/smf_test.cpp b/smf_test.cpp
index 7517d7dd..340c299b 100644
--- a/smf_test.cpp
+++ b/smf_test.cpp
@@ -25,24 +25,308 @@
 
 #include "test_tools.hpp"
 
+#include <type_traits>
+#include <utility>                      // move()
+
 class smf_test
 {
   public:
     static void test()
         {
-        test_something();
+        test_classes();
         }
 
   private:
-    static void test_something();
+    static void test_classes();
+};
+
+struct can_move
+{
+};
+
+can_move moveable_instance;
+
+struct no_can_move
+{
+    no_can_move()                              = default;
+    ~no_can_move()                             = default;
+    no_can_move(no_can_move const&)            = default;
+    no_can_move(no_can_move&&)                 = delete;
+    no_can_move& operator=(no_can_move const&) = default;
+    no_can_move& operator=(no_can_move&&)      = delete;
+};
+
+// Classes for unit testing.
+//
+// See the smf_test::test_classes() documentation below for a key
+// to class names.
+
+/// This struct is an 'aggregate'.
+///
+/// It is important to test an aggregate because this:
+///   A1111 a1111 {smf_mechanics::ambiguator<A1111>{}};
+/// isn't ambiguous; instead, it has too many initializers. See:
+///   https://lists.nongnu.org/archive/html/lmi/2022-08/msg00005.html
+
+struct A1111
+{
+};
+
+struct C0000
+{
+    C0000()                        = default;
+    ~C0000()                       = default;
+    C0000(C0000 const&)            = default;
+    C0000(C0000&&)                 = default;
+    C0000& operator=(C0000 const&) = default;
+    C0000& operator=(C0000&&)      = default;
+};
+
+struct C0101
+{
+    C0101()                        = default;
+    ~C0101()                       = default;
+    C0101(C0101 const&)            = default;
+//  C0101(C0101&&)                 // not declared
+    C0101& operator=(C0101 const&) = default;
+//  C0101& operator=(C0101&&)      // not declared
+};
+
+struct C0202
+{
+    C0202()                        = default;
+    ~C0202()                       = default;
+    C0202(C0202 const&)            = default;
+    C0202& operator=(C0202 const&) = default;
+  protected:
+    C0202(C0202&&)                 = default;
+    C0202& operator=(C0202&&)      = default;
+};
+
+struct C0303
+{
+    C0303()                        = default;
+    ~C0303()                       = default;
+    C0303(C0303 const&)            = default;
+    C0303(C0303&&)                 = delete;
+    C0303& operator=(C0303 const&) = default;
+    C0303& operator=(C0303&&)      = delete;
 };
 
-void smf_test::test_something()
+#if defined LMI_CLANG
+#   pragma clang diagnostic push
+#   pragma clang diagnostic ignored "-Wdefaulted-function-deleted"
+#endif // defined LMI_CLANG
+struct C0404 : public no_can_move
 {
+    C0404()                        = default;
+    ~C0404()                       = default;
+    C0404(C0404 const&)            = default;
+    C0404(C0404&&)                 = default; // implicitly deleted
+    C0404& operator=(C0404 const&) = default;
+    C0404& operator=(C0404&&)      = default; // implicitly deleted
+};
+
+struct C0505
+{
+    C0505()                        = default;
+    ~C0505()                       = default;
+    C0505(C0505 const&)            = default;
+    C0505(C0505&&)                 = default;
+    C0505& operator=(C0505 const&) = default; // implicitly deleted
+    C0505& operator=(C0505&&)      = default; // implicitly deleted
+    // reference or const member:
+    //  - allows copy and move construction
+    //  - inhibits copy and move assignment
+    can_move& cm_ {moveable_instance};
+    int const i_  {};
+};
+#if defined LMI_CLANG
+#   pragma clang diagnostic pop
+#endif // defined LMI_CLANG
+
+struct C3030
+{
+    C3030()                        = default;
+    ~C3030()                       = default;
+    C3030(C3030 const&)            = delete;
+    C3030(C3030&&)                 = default;
+    C3030& operator=(C3030 const&) = delete;
+    C3030& operator=(C3030&&)      = default;
+};
+
+/// Class names are generally one capital letter and four digits,
+/// signifying {cp_ctor, mv_ctor, cp_assign, mv_assign}:
+///   0 = user declared as defaulted
+///   1 = not user declared
+///   2 = explicitly defaulted, but inaccessible
+///   3 = explicitly deleted
+///   4 = implicitly deleted [due to base]
+///   5 = unassignable [due to reference or const member]
+///
+/// properties:
+///   p: is_copy_constructible
+///   q: is_move_constructible
+///   r: equiplausibly_constructible
+///   s: is_copy_assignable
+///   t: is_move_assignable
+///   u: equiplausibly_assignable
+///   v: move construct has move semantics
+///   w: move assign has move semantics
+///   y: move construct compiles without error
+///   z: move assign compiles without error
+///
+/// 1111 0000 0101 0202 0303 0404 0505 3030
+///   +    +    +    +    +    +    +    -   p
+///   +    +    +    -    -    +    +    +   q
+///   +    +    -    +    +    -    +    +   r
+///   +    +    +    +    +    +    -    -   s
+///   +    +    +    -    -    +    -    +   t
+///   +    +    -    +    +    -    +    +   u
+///   +    +    -    -    -    -    +    +   v
+///   +    +    -    -    -    -    -    +   w
+///   +    +    +    -    -    +    +    +   y
+///   +    +    +    -    -    +    -    +   z
+///
+/// Hypotheses:
+///   r ≡ u [need example to falsify this]
+///   v ≡ q ∧ r
+///   w ≡ t ∧ u
+///   y ≡ q
+///   z ≡ t
+
+void smf_test::test_classes()
+{
+    using namespace smf_mechanics;
+
+    // Reproduce each element of the table above except {y,z}.
+    //
+    // It might also be interesting to explore such properties as:
+    //   std::copy_constructible
+    //   std::move_constructible
+    //   std::copyable
+    //   std::movable
+
+    static_assert( std::is_aggregate_v         <A1111>);
+    static_assert( std::is_copy_constructible_v<A1111>); // p
+    static_assert( std::is_move_constructible_v<A1111>); // q
+    static_assert( equiplausibly_constructible <A1111>); // r
+    static_assert( std::is_copy_assignable_v   <A1111>); // s
+    static_assert( std::is_move_assignable_v   <A1111>); // t
+    static_assert( equiplausibly_assignable    <A1111>); // u
+    static_assert( well_move_constructible     <A1111>);
+    static_assert( well_move_assignable        <A1111>);
+
+    static_assert( std::is_copy_constructible_v<C0000>);
+    static_assert( std::is_move_constructible_v<C0000>);
+    static_assert( equiplausibly_constructible <C0000>);
+    static_assert( std::is_copy_assignable_v   <C0000>);
+    static_assert( std::is_move_assignable_v   <C0000>);
+    static_assert( equiplausibly_assignable    <C0000>);
+    static_assert( well_move_constructible     <C0000>);
+    static_assert( well_move_assignable        <C0000>);
+
+    static_assert( std::is_copy_constructible_v<C0101>);
+    static_assert( std::is_move_constructible_v<C0101>);
+    static_assert(!equiplausibly_constructible <C0101>);
+    static_assert( std::is_copy_assignable_v   <C0101>);
+    static_assert( std::is_move_assignable_v   <C0101>);
+    static_assert(!equiplausibly_assignable    <C0101>);
+    static_assert(!well_move_constructible     <C0101>);
+    static_assert(!well_move_assignable        <C0101>);
+
+    static_assert( std::is_copy_constructible_v<C0202>);
+    static_assert(!std::is_move_constructible_v<C0202>);
+    static_assert( equiplausibly_constructible <C0202>);
+    static_assert( std::is_copy_assignable_v   <C0202>);
+    static_assert(!std::is_move_assignable_v   <C0202>);
+    static_assert( equiplausibly_assignable    <C0202>);
+    static_assert(!well_move_constructible     <C0202>);
+    static_assert(!well_move_assignable        <C0202>);
+
+    static_assert( std::is_copy_constructible_v<C0303>);
+    static_assert(!std::is_move_constructible_v<C0303>);
+    static_assert( equiplausibly_constructible <C0303>);
+    static_assert( std::is_copy_assignable_v   <C0303>);
+    static_assert(!std::is_move_assignable_v   <C0303>);
+    static_assert( equiplausibly_assignable    <C0303>);
+    static_assert(!well_move_constructible     <C0303>);
+    static_assert(!well_move_assignable        <C0303>);
+
+    static_assert( std::is_copy_constructible_v<C0404>);
+    static_assert( std::is_move_constructible_v<C0404>);
+    static_assert(!equiplausibly_constructible <C0404>);
+    static_assert( std::is_copy_assignable_v   <C0404>);
+    static_assert( std::is_move_assignable_v   <C0404>);
+    static_assert(!equiplausibly_assignable    <C0404>);
+    static_assert(!well_move_constructible     <C0404>);
+    static_assert(!well_move_assignable        <C0404>);
+
+    static_assert( std::is_copy_constructible_v<C0505>);
+    static_assert( std::is_move_constructible_v<C0505>);
+    static_assert( equiplausibly_constructible <C0505>);
+    static_assert(!std::is_copy_assignable_v   <C0505>);
+    static_assert(!std::is_move_assignable_v   <C0505>);
+    static_assert( equiplausibly_assignable    <C0505>);
+    static_assert( well_move_constructible     <C0505>);
+    static_assert(!well_move_assignable        <C0505>);
+
+    static_assert(!std::is_copy_constructible_v<C3030>);
+    static_assert( std::is_move_constructible_v<C3030>);
+    static_assert( equiplausibly_constructible <C3030>);
+    static_assert(!std::is_copy_assignable_v   <C3030>);
+    static_assert( std::is_move_assignable_v   <C3030>);
+    static_assert( equiplausibly_assignable    <C3030>);
+    static_assert( well_move_constructible     <C3030>);
+    static_assert( well_move_assignable        <C3030>);
+
+    // Instantiate unit-testing classes, and attempt to move them.
+    //
+    // Copying could of course be tested as well as moving.
+
+    A1111 t_A1111 {};
+    C0000 t_C0000 {};
+    C0101 t_C0101 {};
+    C0202 t_C0202 {};
+    C0303 t_C0303 {};
+    C0404 t_C0404 {};
+    C0505 t_C0505 {};
+    C3030 t_C3030 {};
+
+    A1111 u_A1111 {std::move(t_A1111)};
+    C0000 u_C0000 {std::move(t_C0000)};
+    C0101 u_C0101 {std::move(t_C0101)};
+//  C0202 u_C0202 {std::move(t_C0202)}; // protected
+//  C0303 u_C0303 {std::move(t_C0303)}; // deleted
+    C0404 u_C0404 {std::move(t_C0404)};
+    C0505 u_C0505 {std::move(t_C0505)};
+    C3030 u_C3030 {std::move(t_C3030)};
+
+    A1111 v_A1111; v_A1111 = std::move(t_A1111);
+    C0000 v_C0000; v_C0000 = std::move(t_C0000);
+    C0101 v_C0101; v_C0101 = std::move(t_C0101);
+//  C0202 v_C0202; v_C0202 = std::move(t_C0202); // protected
+//  C0303 v_C0303; v_C0303 = std::move(t_C0303); // deleted
+    C0404 v_C0404; v_C0404 = std::move(t_C0404);
+//  C0505 v_C0505; v_C0505 = std::move(t_C0505); // implicitly deleted
+    C3030 v_C3030; v_C3030 = std::move(t_C3030);
+
+    stifle_unused_warning(t_C0202);
+    stifle_unused_warning(t_C0303);
+
+    stifle_unused_warning(u_A1111);
+    stifle_unused_warning(u_C0000);
+    stifle_unused_warning(u_C0101);
+//  stifle_unused_warning(u_C0202);
+//  stifle_unused_warning(u_C0303);
+    stifle_unused_warning(u_C0404);
+    stifle_unused_warning(u_C0505);
+    stifle_unused_warning(u_C3030);
 }
 
 int test_main(int, char*[])
 {
     smf_test::test();
+
     return EXIT_SUCCESS;
 }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]