Implement P0848R3, Conditionally Trivial Special Member Functions.

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
This commit is contained in:
Jason Merrill 2019-08-15 08:38:50 -04:00 committed by Jason Merrill
parent 84cc60bf83
commit c735f8f1a0
9 changed files with 233 additions and 20 deletions

View 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

View 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)));

View 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);

View 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;

View 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)
{

View 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

View 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;

View File

@ -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>>);

View File

@ -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>>);