Implement P0848R3, Conditionally Trivial Special Member Functions.
authorJason Merrill <jason@redhat.com>
Thu, 15 Aug 2019 12:38:50 +0000 (08:38 -0400)
committerJason Merrill <jason@gcc.gnu.org>
Thu, 15 Aug 2019 12:38:50 +0000 (08:38 -0400)
With Concepts, overloads of special member functions can differ in
constraints, and this paper clarifies how that affects class properties: if
a class has a more constrained trivial copy constructor and a less
constrained non-trivial copy constructor, it is still trivially copyable.

* tree.c (special_memfn_p): New.
* class.c (add_method): When overloading, hide ineligible special
member fns.
(check_methods): Set TYPE_HAS_COMPLEX_* here.
* decl.c (grok_special_member_properties): Not here.
* name-lookup.c (push_class_level_binding_1): Move overloaded
functions case down, accept FUNCTION_DECL as target_decl.

From-SVN: r274534

gcc/cp/ChangeLog
gcc/cp/class.c
gcc/cp/cp-tree.h
gcc/cp/decl.c
gcc/cp/name-lookup.c
gcc/cp/tree.c
gcc/testsuite/g++.dg/concepts/pr89036.C
gcc/testsuite/g++.dg/cpp2a/cond-triv1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/cond-triv1a.C [new file with mode: 0644]

index 9eeba3dadc337d2a8ef1d5e7a65caf17d6d6020b..25172ace36fcfaca64faac789c1c5bba66b39de4 100644 (file)
@@ -1,3 +1,14 @@
+2019-08-14  Jason Merrill  <jason@redhat.com>
+
+       Implement P0848R3, Conditionally Trivial Special Member Functions.
+       * tree.c (special_memfn_p): New.
+       * class.c (add_method): When overloading, hide ineligible special
+       member fns.
+       (check_methods): Set TYPE_HAS_COMPLEX_* here.
+       * decl.c (grok_special_member_properties): Not here.
+       * name-lookup.c (push_class_level_binding_1): Move overloaded
+       functions case down, accept FUNCTION_DECL as target_decl.
+
 2019-08-14  Jonathan Wakely  <jwakely@redhat.com>
 
        PR c++/91436
index b61152c7e72f03f7ca1c6597e7dc257ffac4f39f..cc53b15401a309e1b4b6028b3b576660c2d5479b 100644 (file)
@@ -994,6 +994,9 @@ add_method (tree type, tree method, bool via_using)
   tree *slot = find_member_slot (type, DECL_NAME (method));
   tree current_fns = slot ? *slot : NULL_TREE;
 
+  /* See below.  */
+  int losem = -1;
+
   /* Check to see if we've already got this method.  */
   for (ovl_iterator iter (current_fns); iter; ++iter)
     {
@@ -1070,9 +1073,48 @@ add_method (tree type, tree method, bool via_using)
       if (compparms (parms1, parms2)
          && (!DECL_CONV_FN_P (fn)
              || same_type_p (TREE_TYPE (fn_type),
-                             TREE_TYPE (method_type)))
-          && equivalently_constrained (fn, method))
+                             TREE_TYPE (method_type))))
        {
+          if (!equivalently_constrained (fn, method))
+           {
+             special_function_kind sfk = special_memfn_p (method);
+
+             if (sfk == sfk_none)
+               /* Non-special member functions coexist if they are not
+                  equivalently constrained.  */
+               continue;
+
+             /* P0848: For special member functions, deleted, unsatisfied, or
+                less constrained overloads are ineligible.  We implement this
+                by removing them from CLASSTYPE_MEMBER_VEC.  Destructors don't
+                use the notion of eligibility, and the selected destructor can
+                be deleted, but removing unsatisfied or less constrained
+                overloads has the same effect as overload resolution.  */
+             bool dtor = (sfk == sfk_destructor);
+             if (losem == -1)
+               losem = ((!dtor && DECL_DELETED_FN (method))
+                        || !constraints_satisfied_p (method));
+             bool losef = ((!dtor && DECL_DELETED_FN (fn))
+                           || !constraints_satisfied_p (fn));
+             int win;
+             if (losem || losef)
+               win = losem - losef;
+             else
+               win = more_constrained (fn, method);
+             if (win > 0)
+               /* Leave FN in the method vec, discard METHOD.  */
+               return false;
+             else if (win < 0)
+               {
+                 /* Remove FN, add METHOD.  */
+                 current_fns = iter.remove_node (current_fns);
+                 continue;
+               }
+             else
+               /* Let them coexist for now.  */
+               continue;
+           }
+
          /* If these are versions of the same function, process and
             move on.  */
          if (TREE_CODE (fn) == FUNCTION_DECL
@@ -4468,11 +4510,6 @@ check_methods (tree t)
              vec_safe_push (CLASSTYPE_PURE_VIRTUALS (t), x);
          }
 
-       /* All user-provided destructors are non-trivial.
-          Constructors and assignment ops are handled in
-          grok_special_member_properties.  */
-       if (DECL_DESTRUCTOR_P (x) && user_provided_p (x))
-         TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t) = 1;
        if (!DECL_VIRTUAL_P (x)
            && lookup_attribute ("transaction_safe_dynamic",
                                 DECL_ATTRIBUTES (x)))
@@ -4480,6 +4517,51 @@ check_methods (tree t)
                    "%<transaction_safe_dynamic%> may only be specified for "
                    "a virtual function");
       }
+
+  /* Check whether the eligible special member functions (P0848) are
+     user-provided.  add_method arranged that the CLASSTYPE_MEMBER_VEC only
+     has the eligible ones; TYPE_FIELDS also contains ineligible overloads,
+     which is why this needs to be separate from the loop above.  */
+
+  if (tree dtor = CLASSTYPE_DESTRUCTOR (t))
+    {
+      if (TREE_CODE (dtor) == OVERLOAD)
+       {
+         /* P0848: At the end of the definition of a class, overload
+            resolution is performed among the prospective destructors declared
+            in that class with an empty argument list to select the destructor
+            for the class, also known as the selected destructor. The program
+            is ill-formed if overload resolution fails. */
+         auto_diagnostic_group d;
+         error_at (location_of (t), "destructor for %qT is ambiguous", t);
+         print_candidates (dtor);
+       }
+      else if (user_provided_p (dtor))
+       TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t) = true;
+    }
+
+  for (ovl_iterator i (CLASSTYPE_CONSTRUCTORS (t)); i; ++i)
+    {
+      tree fn = *i;
+      if (!user_provided_p (fn))
+       /* Might be trivial.  */;
+      else if (copy_fn_p (fn))
+       TYPE_HAS_COMPLEX_COPY_CTOR (t) = true;
+      else if (move_fn_p (fn))
+       TYPE_HAS_COMPLEX_MOVE_CTOR (t) = true;
+    }
+
+  for (ovl_iterator i (get_class_binding_direct (t, assign_op_identifier));
+       i; ++i)
+    {
+      tree fn = *i;
+      if (!user_provided_p (fn))
+       /* Might be trivial.  */;
+      else if (copy_fn_p (fn))
+       TYPE_HAS_COMPLEX_COPY_ASSIGN (t) = true;
+      else if (move_fn_p (fn))
+       TYPE_HAS_COMPLEX_MOVE_ASSIGN (t) = true;
+    }
 }
 
 /* FN is a constructor or destructor.  Clone the declaration to create
@@ -4950,7 +5032,7 @@ set_method_tm_attributes (tree t)
 /* Returns true if FN is a default constructor.  */
 
 bool
-default_ctor_p (tree fn)
+default_ctor_p (const_tree fn)
 {
   return (DECL_CONSTRUCTOR_P (fn)
          && sufficient_parms_p (FUNCTION_FIRST_USER_PARMTYPE (fn)));
index bdb7778c04b8880bed9ce6bad14086c35e8c1d8b..05f91861b42417c0ffe31396214b6320da5ebd10 100644 (file)
@@ -6313,7 +6313,7 @@ extern void determine_key_method          (tree);
 extern void check_for_override                 (tree, tree);
 extern void push_class_stack                   (void);
 extern void pop_class_stack                    (void);
-extern bool default_ctor_p                     (tree);
+extern bool default_ctor_p                     (const_tree);
 extern bool type_has_user_nondefault_constructor (tree);
 extern tree in_class_defaulted_default_constructor (tree);
 extern bool user_provided_p                    (tree);
@@ -7322,6 +7322,7 @@ extern tree cp_build_qualified_type_real  (tree, int, tsubst_flags_t);
 extern bool cv_qualified_p                     (const_tree);
 extern tree cv_unqualified                     (tree);
 extern special_function_kind special_function_p (const_tree);
+extern special_function_kind special_memfn_p   (const_tree);
 extern int count_trees                         (tree);
 extern int char_type_p                         (tree);
 extern void verify_stmt_tree                   (tree);
index ff3b90dba5422fc96708ec609dc85091ece64ca5..08b7baa40e004b78aa6b46240c59403367acb0fb 100644 (file)
@@ -13534,15 +13534,11 @@ grok_special_member_properties (tree decl)
             are no other parameters or else all other parameters have
             default arguments.  */
          TYPE_HAS_COPY_CTOR (class_type) = 1;
-         if (user_provided_p (decl))
-           TYPE_HAS_COMPLEX_COPY_CTOR (class_type) = 1;
          if (ctor > 1)
            TYPE_HAS_CONST_COPY_CTOR (class_type) = 1;
        }
       else if (sufficient_parms_p (FUNCTION_FIRST_USER_PARMTYPE (decl)))
        TYPE_HAS_DEFAULT_CONSTRUCTOR (class_type) = 1;
-      else if (move_fn_p (decl) && user_provided_p (decl))
-       TYPE_HAS_COMPLEX_MOVE_CTOR (class_type) = 1;
       else if (is_list_ctor (decl))
        TYPE_HAS_LIST_CTOR (class_type) = 1;
 
@@ -13563,13 +13559,9 @@ grok_special_member_properties (tree decl)
       if (assop)
        {
          TYPE_HAS_COPY_ASSIGN (class_type) = 1;
-         if (user_provided_p (decl))
-           TYPE_HAS_COMPLEX_COPY_ASSIGN (class_type) = 1;
          if (assop != 1)
            TYPE_HAS_CONST_COPY_ASSIGN (class_type) = 1;
        }
-      else if (move_fn_p (decl) && user_provided_p (decl))
-       TYPE_HAS_COMPLEX_MOVE_ASSIGN (class_type) = 1;
     }
   else if (IDENTIFIER_CONV_OP_P (DECL_NAME (decl)))
     TYPE_HAS_CONVERSION (class_type) = true;
index 16c74287bb1c5dc10b880c9b4e179a18a7da8b17..5f5ff81f4054b435e2c66ea357f6e72b7a173b94 100644 (file)
@@ -4504,9 +4504,6 @@ push_class_level_binding_1 (tree name, tree x)
                binding->type = NULL_TREE;
            }
        }
-      else if (TREE_CODE (target_decl) == OVERLOAD
-              && OVL_P (target_bval))
-       old_decl = bval;
       else if (TREE_CODE (decl) == USING_DECL
               && TREE_CODE (bval) == USING_DECL
               && same_type_p (USING_DECL_SCOPE (decl),
@@ -4525,6 +4522,9 @@ push_class_level_binding_1 (tree name, tree x)
       else if (TREE_CODE (bval) == USING_DECL
               && OVL_P (target_decl))
        return true;
+      else if (OVL_P (target_decl)
+              && OVL_P (target_bval))
+       old_decl = bval;
 
       if (old_decl && binding->scope == class_binding_level)
        {
index 27bc35198bba272e2f11e7fa574abc9470131108..bca92100621a49edd28ef8a493192411b8f83b30 100644 (file)
@@ -5015,6 +5015,31 @@ special_function_p (const_tree decl)
   return sfk_none;
 }
 
+/* As above, but only if DECL is a special member function as per 11.3.3
+   [special]: default/copy/move ctor, copy/move assignment, or destructor.  */
+
+special_function_kind
+special_memfn_p (const_tree decl)
+{
+  switch (special_function_kind sfk = special_function_p (decl))
+    {
+    case sfk_constructor:
+      if (!default_ctor_p (decl))
+       break;
+      gcc_fallthrough();
+    case sfk_copy_constructor:
+    case sfk_copy_assignment:
+    case sfk_move_assignment:
+    case sfk_move_constructor:
+    case sfk_destructor:
+      return sfk;
+
+    default:
+      break;
+    }
+  return sfk_none;
+}
+
 /* Returns nonzero if TYPE is a character type, including wchar_t.  */
 
 int
index f83ef8b96793b25b3f661181eb9a332d3c43502f..5dcd649d1f851235cbd62f9ba9facf25404de9e8 100644 (file)
@@ -6,3 +6,13 @@ struct Y {
   ~Y() requires(true) = default;
   ~Y() requires(false) {}
 };
+
+Y<int> y;
+
+template<typename T>
+struct X {
+  ~X() requires(sizeof(T) == 8) = default;
+  ~X() requires(sizeof(T) != 8) {}
+};
+
+X<int> x;
diff --git a/gcc/testsuite/g++.dg/cpp2a/cond-triv1.C b/gcc/testsuite/g++.dg/cpp2a/cond-triv1.C
new file mode 100644 (file)
index 0000000..8f5806e
--- /dev/null
@@ -0,0 +1,46 @@
+// Testcase from P0848R0
+// { dg-do compile { target concepts } }
+
+#include <type_traits>
+
+template <typename T>
+class optional
+{
+  struct empty {};
+  union {
+    empty _ = { };
+    T value;
+  };
+  bool engaged = false;
+
+public:
+  constexpr optional() = default;
+
+  constexpr optional(optional const&)
+    requires std::is_trivially_copy_constructible_v<T>
+    = default;
+  constexpr optional(optional const& o)
+    : engaged (o.engaged)
+  {
+    if (engaged)
+      new (&value) T (o.value);
+  }
+
+  ~optional()
+    requires std::is_trivially_destructible_v<T>
+    = default;
+  ~optional()
+  {
+    if (engaged)
+      value.~T();
+  }
+
+  // ...
+};
+
+struct A { A(); A(const A&); ~A(); };
+
+static_assert(std::is_trivially_copy_constructible_v<optional<int>>);
+static_assert(!std::is_trivially_copy_constructible_v<optional<A>>);
+static_assert(std::is_trivially_destructible_v<optional<int>>);
+static_assert(!std::is_trivially_destructible_v<optional<A>>);
diff --git a/gcc/testsuite/g++.dg/cpp2a/cond-triv1a.C b/gcc/testsuite/g++.dg/cpp2a/cond-triv1a.C
new file mode 100644 (file)
index 0000000..febd109
--- /dev/null
@@ -0,0 +1,46 @@
+// Like cond-triv1.C, but with the declaration order swapped.
+// { dg-do compile { target concepts } }
+
+#include <type_traits>
+
+template <typename T>
+class optional
+{
+  struct empty {};
+  union {
+    empty _ = { };
+    T value;
+  };
+  bool engaged = false;
+
+public:
+  constexpr optional() = default;
+
+  constexpr optional(optional const& o)
+    : engaged (o.engaged)
+  {
+    if (engaged)
+      new (&value) T (o.value);
+  }
+  constexpr optional(optional const&)
+    requires std::is_trivially_copy_constructible_v<T>
+    = default;
+
+  ~optional()
+  {
+    if (engaged)
+      value.~T();
+  }
+  ~optional()
+    requires std::is_trivially_destructible_v<T>
+    = default;
+
+  // ...
+};
+
+struct A { A(); A(const A&); ~A(); };
+
+static_assert(std::is_trivially_copy_constructible_v<optional<int>>);
+static_assert(!std::is_trivially_copy_constructible_v<optional<A>>);
+static_assert(std::is_trivially_destructible_v<optional<int>>);
+static_assert(!std::is_trivially_destructible_v<optional<A>>);