OK for trunk?
-- >8 --
This adds detailed explanatory diagnostics for the three builtins used
for trivial relocatable, nothrow relocatable, and replaceable.
It also fixes the checks for move construction (and assignment) to do
overload resolution rather than just checking the first viable move
constructor candidate found, as specified by the standard, because in
some cases there may be more than one viable move constructor and we
should complain about the one we would select (or if the choice is
ambiguous).
gcc/cp/ChangeLog:
* constraint.cc (diagnose_trait_expr):
<case CPTK_IS_NOTHROW_RELOCATABLE>: Explain why not.
<case CPTK_IS_REPLACEABLE: Likewise.
<case CPTK_IS_TRIVIALLY_RELOCATABLE>: Likewise.
* cp-tree.h (get_move_ctor): Declare.
(get_move_assign): Declare.
(trivially_relocatable_type_p): Add explain parameter.
(replaceable_type_p): Likewise.
(nothrow_relocatable_type_p): Likewise.
(object_type_p): Declare.
(referenceable_type_p): Declare.
* method.cc (get_move_ctor): New function.
(get_move_assign): New function.
* semantics.cc (object_type_p): Move to tree.cc.
(referenceable_type_p): Likewise.
(trait_expr_value): Use nothrow_relocatable_type_p.
* tree.cc (object_type_p): Moved from semantics.cc.
(referenceable_type_p): Likewise.
(default_movable_type_p): Explain why not; use new
get_move_{ctor,assign} function.
(replaceable_type_p): Likewise.
(trivially_relocatable_type_p): Explain why not.
(nothrow_relocatable_type_p): New function.
gcc/testsuite/ChangeLog:
* g++.dg/cpp26/trivially-relocatable12.C: New test.
* g++.dg/cpp26/trivially-relocatable13.C: New test.
Signed-off-by: Nathaniel Shead <[email protected]>
---
gcc/cp/constraint.cc | 9 +-
gcc/cp/cp-tree.h | 9 +-
gcc/cp/method.cc | 26 +
gcc/cp/semantics.cc | 36 +-
gcc/cp/tree.cc | 443 ++++++++++++------
.../g++.dg/cpp26/trivially-relocatable12.C | 54 +++
.../g++.dg/cpp26/trivially-relocatable13.C | 106 +++++
7 files changed, 505 insertions(+), 178 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/cpp26/trivially-relocatable12.C
create mode 100644 gcc/testsuite/g++.dg/cpp26/trivially-relocatable13.C
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 0fafd4ff01f..d6bef339f98 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3233,7 +3233,8 @@ diagnose_trait_expr (location_t loc, tree expr, tree args)
}
break;
case CPTK_IS_NOTHROW_RELOCATABLE:
- inform (loc, "%qT is not nothrow relocatable", t1);
+ inform (loc, "%qT is not nothrow relocatable, because", t1);
+ nothrow_relocatable_type_p (t1, /*explain=*/true);
break;
case CPTK_IS_OBJECT:
inform (loc, "%qT is not an object type", t1);
@@ -3257,7 +3258,8 @@ diagnose_trait_expr (location_t loc, tree expr, tree args)
inform (loc, "%qT is not a reference", t1);
break;
case CPTK_IS_REPLACEABLE:
- inform (loc, "%qT is not replaceable", t1);
+ inform (loc, "%qT is not replaceable, because", t1);
+ replaceable_type_p (t1, /*explain=*/true);
break;
case CPTK_IS_SAME:
inform (loc, "%q#T is not the same as %q#T", t1, t2);
@@ -3291,7 +3293,8 @@ diagnose_trait_expr (location_t loc, tree expr, tree args)
is_trivially_xible (BIT_NOT_EXPR, t1, NULL_TREE, /*explain=*/true);
break;
case CPTK_IS_TRIVIALLY_RELOCATABLE:
- inform (loc, "%qT is not trivially relocatable", t1);
+ inform (loc, "%qT is not trivially relocatable, because", t1);
+ trivially_relocatable_type_p (t1, /*explain=*/true);
break;
case CPTK_IS_UNBOUNDED_ARRAY:
inform (loc, "%qT is not an unbounded array", t1);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index f24063372cd..571f5e2b300 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7673,6 +7673,8 @@ extern int num_artificial_parms_for
(const_tree);
extern tree make_alias_for (tree, tree);
extern tree get_copy_ctor (tree, tsubst_flags_t);
extern tree get_copy_assign (tree);
+extern tree get_move_ctor (tree, tsubst_flags_t);
+extern tree get_move_assign (tree, tsubst_flags_t);
extern tree get_default_ctor (tree);
extern tree get_dtor (tree, tsubst_flags_t);
extern tree build_stub_object (tree);
@@ -8375,8 +8377,9 @@ extern bool pod_type_p
(const_tree);
extern bool layout_pod_type_p (const_tree);
extern bool std_layout_type_p (const_tree);
extern bool trivial_type_p (const_tree);
-extern bool trivially_relocatable_type_p (tree);
-extern bool replaceable_type_p (tree);
+extern bool trivially_relocatable_type_p (tree, bool = false);
+extern bool nothrow_relocatable_type_p (tree, bool = false);
+extern bool replaceable_type_p (tree, bool = false);
extern bool trivially_copyable_p (const_tree);
extern bool type_has_unique_obj_representations (const_tree, bool = false);
extern bool scalarish_type_p (const_tree);
@@ -8488,6 +8491,8 @@ extern const struct scoped_attribute_specs
std_attribute_table;
extern const struct scoped_attribute_specs internal_attribute_table;
extern tree make_ptrmem_cst (tree, tree);
extern tree cp_build_type_attribute_variant (tree, tree);
+extern bool object_type_p (const_tree);
+extern bool referenceable_type_p (const_tree);
extern tree cp_build_reference_type (tree, bool);
extern tree move (tree);
extern tree cp_build_qualified_type (tree, int,
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index bc721a5d7e3..370f9930d26 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -2218,6 +2218,32 @@ get_copy_assign (tree type)
return fn;
}
+/* Locate the move ctor of TYPE. */
+
+tree
+get_move_ctor (tree type, tsubst_flags_t complain)
+{
+ tree argtype = build_stub_type (type, TYPE_UNQUALIFIED, /*rvalue=*/true);
+ tree fn = locate_fn_flags (type, complete_ctor_identifier, argtype,
+ LOOKUP_NORMAL, complain);
+ if (fn == error_mark_node)
+ return NULL_TREE;
+ return fn;
+}
+
+/* Locate the move assignment operator for an lvalue of TYPE. */
+
+tree
+get_move_assign (tree type, tsubst_flags_t complain)
+{
+ tree argtype = build_stub_type (type, TYPE_UNQUALIFIED, /*rvalue=*/true);
+ tree fn = locate_fn_flags (type, assign_op_identifier, argtype,
+ LOOKUP_NORMAL, complain);
+ if (fn == error_mark_node)
+ return NULL_TREE;
+ return fn;
+}
+
/* walk_tree helper function for is_trivially_xible. If *TP is a call,
return it if it calls something other than a trivial special member
function. */
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 1d3ee5b6745..794e5674ae6 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -13468,28 +13468,6 @@ fold_builtin_is_corresponding_member (location_t loc,
int nargs,
fold_convert (TREE_TYPE (arg1), arg2)));
}
-/* [basic.types] 8. True iff TYPE is an object type. */
-
-static bool
-object_type_p (const_tree type)
-{
- return (TREE_CODE (type) != FUNCTION_TYPE
- && !TYPE_REF_P (type)
- && !VOID_TYPE_P (type));
-}
-
-/* [defns.referenceable] True iff TYPE is a referenceable type. */
-
-static bool
-referenceable_type_p (const_tree type)
-{
- return (TYPE_REF_P (type)
- || object_type_p (type)
- || (FUNC_OR_METHOD_TYPE_P (type)
- && type_memfn_quals (type) == TYPE_UNQUALIFIED
- && type_memfn_rqual (type) == REF_QUAL_NONE));
-}
-
/* Actually evaluates the trait. */
static bool
@@ -13660,19 +13638,7 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree
type2)
return expr_noexcept_p (build_invoke (type1, type2, tf_none), tf_none);
case CPTK_IS_NOTHROW_RELOCATABLE:
- if (trivially_relocatable_type_p (type1))
- return true;
- else
- {
- type1 = strip_array_types (type1);
- if (!referenceable_type_p (type1))
- return false;
- tree arg = make_tree_vec (1);
- TREE_VEC_ELT (arg, 0)
- = cp_build_reference_type (type1, /*rval=*/true);
- return (is_nothrow_xible (INIT_EXPR, type1, arg)
- && is_nothrow_xible (BIT_NOT_EXPR, type1, NULL_TREE));
- }
+ return nothrow_relocatable_type_p (type1);
case CPTK_IS_OBJECT:
return object_type_p (type1);
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index 7cfdfca32ae..16dc5e91220 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -1299,6 +1299,27 @@ vla_type_p (tree t)
return false;
}
+/* [basic.types] 8. True iff TYPE is an object type. */
+
+bool
+object_type_p (const_tree type)
+{
+ return (TREE_CODE (type) != FUNCTION_TYPE
+ && !TYPE_REF_P (type)
+ && !VOID_TYPE_P (type));
+}
+
+/* [defns.referenceable] True iff TYPE is a referenceable type. */
+
+bool
+referenceable_type_p (const_tree type)
+{
+ return (TYPE_REF_P (type)
+ || object_type_p (type)
+ || (FUNC_OR_METHOD_TYPE_P (type)
+ && type_memfn_quals (type) == TYPE_UNQUALIFIED
+ && type_memfn_rqual (type) == REF_QUAL_NONE));
+}
/* Return a reference type node of MODE referring to TO_TYPE. If MODE
is VOIDmode the standard pointer mode will be picked. If RVAL is
@@ -4814,80 +4835,81 @@ trivial_type_p (const_tree t)
[class.prop]. */
static bool
-default_movable_type_p (tree t)
+default_movable_type_p (tree t, bool explain = false)
{
if (!CLASS_TYPE_P (t) || !COMPLETE_TYPE_P (t))
- return false;
+ {
+ if (explain)
+ inform (location_of (t), "%qT is not a complete class type", t);
+ return false;
+ }
if (CLASSTYPE_LAZY_DESTRUCTOR (t))
lazily_declare_fn (sfk_destructor, t);
if (tree dtor = CLASSTYPE_DESTRUCTOR (t))
- if (user_provided_p (dtor) || DECL_DELETED_FN (dtor))
- return false;
-
- tree copy_ctor = NULL_TREE, move_ctor = NULL_TREE;
- tree copy_assign = NULL_TREE, move_assign = NULL_TREE;
- if (CLASSTYPE_LAZY_MOVE_CTOR (t))
- move_ctor = lazily_declare_fn (sfk_move_constructor, t);
- if (CLASSTYPE_LAZY_MOVE_ASSIGN (t))
- move_assign = lazily_declare_fn (sfk_move_assignment, t);
- if (!move_ctor)
- for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (t)); iter; ++iter)
- if (TREE_CODE (*iter) == FUNCTION_DECL)
+ {
+ if (user_provided_p (dtor))
{
- if (copy_fn_p (*iter))
- copy_ctor = *iter;
- else if (move_fn_p (*iter))
- {
- move_ctor = *iter;
- break;
- }
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (dtor),
+ "%qT has a user-provided destructor", t);
+ return false;
}
- if (!move_assign)
- for (ovl_iterator iter (get_class_binding_direct (t,
- assign_op_identifier));
- iter; ++iter)
- if (TREE_CODE (*iter) == FUNCTION_DECL)
+ if (DECL_DELETED_FN (dtor))
{
- if (copy_fn_p (*iter))
- copy_assign = *iter;
- else if (move_fn_p (*iter))
- {
- move_assign = *iter;
- break;
- }
+ if (explain && !maybe_explain_implicit_delete (dtor))
+ inform (DECL_SOURCE_LOCATION (dtor),
+ "%qD is defined as deleted", dtor);
+ return false;
}
+ }
+
+ /* We should actually perform overload resolution, in case the user has
+ provided multiple candidate move constructors. */
+ deferring_access_check_sentinel dacs (dk_no_check);
+ tree move_ctor = get_move_ctor (t, tf_none);
if (!move_ctor)
{
- if (CLASSTYPE_LAZY_COPY_CTOR (t))
- copy_ctor = lazily_declare_fn (sfk_copy_constructor, t);
- if (!copy_ctor)
- return false;
- if (user_provided_p (copy_ctor)
- || DECL_DELETED_FN (copy_ctor)
- || DECL_CONTEXT (copy_ctor) != t
- || DECL_INHERITED_CTOR (copy_ctor))
- return false;
+ if (explain)
+ get_move_ctor (t, tf_error);
+ return false;
}
- else if (user_provided_p (move_ctor)
- || DECL_DELETED_FN (move_ctor)
- || DECL_CONTEXT (move_ctor) != t
- || DECL_INHERITED_CTOR (move_ctor))
- return false;
+ else if (user_provided_p (move_ctor))
+ {
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (move_ctor),
+ "%qD is user-provided", move_ctor);
+ return false;
+ }
+ else if (DECL_CONTEXT (move_ctor) != t || DECL_INHERITED_CTOR (move_ctor))
+ {
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (move_ctor),
+ "%qD is not a direct member of %qT", move_ctor, t);
+ return false;
+ }
+
+ tree move_assign = get_move_assign (t, tf_none);
if (!move_assign)
{
- if (CLASSTYPE_LAZY_COPY_ASSIGN (t))
- copy_assign = lazily_declare_fn (sfk_copy_assignment, t);
- if (!copy_assign)
- return false;
- if (user_provided_p (copy_assign)
- || DECL_DELETED_FN (copy_assign)
- || DECL_CONTEXT (copy_assign) != t)
- return false;
+ if (explain)
+ get_move_assign (t, tf_error);
+ return false;
}
- else if (user_provided_p (move_assign)
- || DECL_DELETED_FN (move_assign)
- || DECL_CONTEXT (move_assign) != t)
- return false;
+ else if (user_provided_p (move_assign))
+ {
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (move_assign),
+ "%qD is user-provided", move_assign);
+ return false;
+ }
+ else if (DECL_CONTEXT (move_assign) != t)
+ {
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (move_assign),
+ "%qD is not a direct member of %qT", move_assign, t);
+ return false;
+ }
+
return true;
}
@@ -4924,37 +4946,51 @@ union_with_no_declared_special_member_fns (tree t)
[basic.types.general] and [class.prop]. */
bool
-trivially_relocatable_type_p (tree t)
+trivially_relocatable_type_p (tree t, bool explain/*=false*/)
{
t = strip_array_types (t);
if (!CLASS_TYPE_P (t))
- return scalarish_type_p (t);
+ {
+ if (!scalarish_type_p (t))
+ {
+ if (explain)
+ inform (location_of (t), "%qT is not a class or scalar type", t);
+ return false;
+ }
+ return true;
+ }
t = TYPE_MAIN_VARIANT (t);
- if (CLASSTYPE_TRIVIALLY_RELOCATABLE_COMPUTED (t))
+ if (!explain && CLASSTYPE_TRIVIALLY_RELOCATABLE_COMPUTED (t))
return CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT (t);
if (!COMPLETE_TYPE_P (t))
- return false;
+ {
+ if (explain)
+ inform (location_of (t), "%qT is not complete", t);
+ return false;
+ }
- if (!CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT (t)
- && !union_with_no_declared_special_member_fns (t)
- && !default_movable_type_p (t))
+ if (CLASSTYPE_VBASECLASSES (t))
{
+ if (explain)
+ inform (location_of (t), "%qT has virtual bases", t);
nontriv:
CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT (t) = 0;
CLASSTYPE_TRIVIALLY_RELOCATABLE_COMPUTED (t) = 1;
return false;
}
- if (CLASSTYPE_VBASECLASSES (t))
- goto nontriv;
-
if (CLASSTYPE_LAZY_DESTRUCTOR (t))
lazily_declare_fn (sfk_destructor, t);
if (tree dtor = CLASSTYPE_DESTRUCTOR (t))
if (DECL_DELETED_FN (dtor))
- goto nontriv;
+ {
+ if (explain && !maybe_explain_implicit_delete (dtor))
+ inform (DECL_SOURCE_LOCATION (dtor),
+ "%qD is defined as deleted", dtor);
+ goto nontriv;
+ }
tree binfo, base_binfo;
unsigned int i;
@@ -4963,7 +4999,13 @@ trivially_relocatable_type_p (tree t)
{
tree basetype = TREE_TYPE (base_binfo);
if (!trivially_relocatable_type_p (basetype))
- goto nontriv;
+ {
+ if (explain)
+ inform (location_of (t),
+ "base class of type %qT is not trivially relocatable",
+ basetype);
+ goto nontriv;
+ }
}
for (tree field = TYPE_FIELDS (t); field; field = DECL_CHAIN (field))
@@ -4973,102 +5015,187 @@ trivially_relocatable_type_p (tree t)
{
tree type = TREE_TYPE (field);
if (type == error_mark_node)
- goto nontriv;
+ {
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (field),
+ "field %qD is not trivially relocatable", field);
+ goto nontriv;
+ }
if (!TYPE_REF_P (type) && !trivially_relocatable_type_p (type))
- goto nontriv;
+ {
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (field),
+ "field %qD of type %qT is not trivially relocatable",
+ field, type);
+ goto nontriv;
+ }
}
+ if (!CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT (t)
+ && !union_with_no_declared_special_member_fns (t)
+ && !default_movable_type_p (t))
+ {
+ if (explain)
+ {
+ /* At this point CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT will only
+ be cleared if we failed a condition above. */
+ if (TREE_CODE (t) == UNION_TYPE)
+ inform (location_of (t),
+ "%qT is not marked %<trivially_relocatable_if_eligible%>, "
+ "has user-declared special member functions, "
+ "and is not default-movable because:", t);
+ else
+ inform (location_of (t),
+ "%qT is not marked %<trivially_relocatable_if_eligible%>, "
+ "and is not default-movable because:", t);
+ auto_diagnostic_nesting_level adnl;
+ default_movable_type_p (t, /*explain=*/true);
+ }
+ goto nontriv;
+ }
+
+ if (explain)
+ gcc_checking_assert (CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT (t));
CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT (t) = 1;
CLASSTYPE_TRIVIALLY_RELOCATABLE_COMPUTED (t) = 1;
return true;
}
+/* True iff type T is a nothrow relocatable type, as defined in
+ [meta.unary.prop]. */
+
+bool
+nothrow_relocatable_type_p (tree t, bool explain/*=false*/)
+{
+ if (trivially_relocatable_type_p (t))
+ return true;
+
+ t = strip_array_types (t);
+ if (!referenceable_type_p (t))
+ {
+ if (explain)
+ inform (location_of (t), "%qT is not an object, reference, or "
+ "unqualified function type", t);
+ return false;
+ }
+
+ tree arg = make_tree_vec (1);
+ TREE_VEC_ELT (arg, 0)
+ = cp_build_reference_type (t, /*rval=*/true);
+ bool movable = is_nothrow_xible (INIT_EXPR, t, arg);
+ bool destructable = is_nothrow_xible (BIT_NOT_EXPR, t, NULL_TREE);
+ if (!(movable && destructable))
+ {
+ if (explain)
+ {
+ inform (location_of (t),
+ "%qT is not either trivially relocatable, "
+ "or nothrow move-constructible and nothrow destructible", t);
+ {
+ inform (location_of (t),
+ "%qT is not trivially relocatable because:", t);
+ auto_diagnostic_nesting_level adnl;
+ trivially_relocatable_type_p (t, /*explain=*/true);
+ }
+ if (!movable)
+ {
+ inform (location_of (t),
+ "%qT is not nothrow move constructible because:", t);
+ auto_diagnostic_nesting_level adnl;
+ is_nothrow_xible (INIT_EXPR, t, arg, /*explain=*/true);
+ }
+ else if (!destructable)
+ {
+ /* We should never reach here, but see LWG2116. */
+ inform (location_of (t),
+ "%qT is not nothrow destructible because:", t);
+ auto_diagnostic_nesting_level adnl;
+ is_nothrow_xible (BIT_NOT_EXPR, t, NULL_TREE, /*explain=*/true);
+ }
+ }
+ return false;
+ }
+
+ return true;
+}
+
/* Returns 1 iff type T is a replaceable type, as defined in [basic.types]
and [class]. */
bool
-replaceable_type_p (tree t)
+replaceable_type_p (tree t, bool explain/*=false*/)
{
t = strip_array_types (t);
if (cv_qualified_p (t))
- return false;
+ {
+ if (explain)
+ {
+ inform (location_of (t),
+ "%<const%> or %<volatile%> types are not replaceable");
+ }
+ return false;
+ }
if (!CLASS_TYPE_P (t))
- return scalarish_type_p (t);
+ {
+ if (!scalarish_type_p (t))
+ {
+ if (explain)
+ inform (location_of (t), "%qT is not a class or scalar type", t);
+ return false;
+ }
+ return true;
+ }
t = TYPE_MAIN_VARIANT (t);
- if (CLASSTYPE_REPLACEABLE_COMPUTED (t))
+ if (!explain && CLASSTYPE_REPLACEABLE_COMPUTED (t))
return CLASSTYPE_REPLACEABLE_BIT (t);
if (!COMPLETE_TYPE_P (t))
- return false;
+ {
+ if (explain)
+ inform (location_of (t), "%qT is not complete", t);
+ return false;
+ }
- if (!CLASSTYPE_REPLACEABLE_BIT (t)
- && !union_with_no_declared_special_member_fns (t)
- && !default_movable_type_p (t))
+ if (CLASSTYPE_LAZY_DESTRUCTOR (t))
+ lazily_declare_fn (sfk_destructor, t);
+ tree dtor = CLASSTYPE_DESTRUCTOR (t);
+ if (dtor && DECL_DELETED_FN (dtor))
{
+ if (explain && !maybe_explain_implicit_delete (dtor))
+ inform (DECL_SOURCE_LOCATION (dtor),
+ "%qD is defined as deleted", dtor);
nonrepl:
CLASSTYPE_REPLACEABLE_BIT (t) = 0;
CLASSTYPE_REPLACEABLE_COMPUTED (t) = 1;
return false;
}
- if (CLASSTYPE_LAZY_DESTRUCTOR (t))
- lazily_declare_fn (sfk_destructor, t);
- if (tree dtor = CLASSTYPE_DESTRUCTOR (t))
- if (DECL_DELETED_FN (dtor))
- goto nonrepl;
-
- tree copy_ctor = NULL_TREE, move_ctor = NULL_TREE;
- tree copy_assign = NULL_TREE, move_assign = NULL_TREE;
- if (CLASSTYPE_LAZY_MOVE_CTOR (t))
- move_ctor = lazily_declare_fn (sfk_move_constructor, t);
- if (CLASSTYPE_LAZY_MOVE_ASSIGN (t))
- move_assign = lazily_declare_fn (sfk_move_assignment, t);
- if (!move_ctor)
- for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (t)); iter; ++iter)
- if (TREE_CODE (*iter) == FUNCTION_DECL)
- {
- if (copy_fn_p (*iter))
- copy_ctor = *iter;
- else if (move_fn_p (*iter))
- {
- move_ctor = *iter;
- break;
- }
- }
- if (!move_assign)
- for (ovl_iterator iter (get_class_binding_direct (t,
- assign_op_identifier));
- iter; ++iter)
- if (TREE_CODE (*iter) == FUNCTION_DECL)
- {
- if (copy_fn_p (*iter))
- copy_assign = *iter;
- else if (move_fn_p (*iter))
- {
- move_assign = *iter;
- break;
- }
- }
- if (!move_ctor)
- {
- if (CLASSTYPE_LAZY_COPY_CTOR (t))
- copy_ctor = lazily_declare_fn (sfk_copy_constructor, t);
- if (!copy_ctor || DECL_DELETED_FN (copy_ctor))
+ {
+ deferring_access_check_sentinel dacs (dk_no_check);
+ if (!get_move_ctor (t, tf_none))
+ {
+ if (explain)
+ {
+ inform (location_of (t), "%qT is not move-constructible", t);
+ auto_diagnostic_nesting_level adnl;
+ get_move_ctor (t, tf_error);
+ }
goto nonrepl;
- }
- else if (DECL_DELETED_FN (move_ctor))
- goto nonrepl;
- if (!move_assign)
- {
- if (CLASSTYPE_LAZY_COPY_ASSIGN (t))
- copy_assign = lazily_declare_fn (sfk_copy_assignment, t);
- if (!copy_assign || DECL_DELETED_FN (copy_assign))
+ }
+
+ if (!get_move_assign (t, tf_none))
+ {
+ if (explain)
+ {
+ inform (location_of (t), "%qT is not move-assignable", t);
+ auto_diagnostic_nesting_level adnl;
+ get_move_assign (t, tf_error);
+ }
goto nonrepl;
- }
- else if (DECL_DELETED_FN (move_assign))
- goto nonrepl;
+ }
+ }
tree binfo, base_binfo;
unsigned int i;
@@ -5077,7 +5204,12 @@ replaceable_type_p (tree t)
{
tree basetype = TREE_TYPE (base_binfo);
if (!replaceable_type_p (basetype))
- goto nonrepl;
+ {
+ if (explain)
+ inform (location_of (t),
+ "base class of type %qT is not replaceable", basetype);
+ goto nonrepl;
+ }
}
for (tree field = TYPE_FIELDS (t); field; field = DECL_CHAIN (field))
@@ -5087,11 +5219,46 @@ replaceable_type_p (tree t)
{
tree type = TREE_TYPE (field);
if (type == error_mark_node)
- goto nonrepl;
+ {
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (field),
+ "field %qD is not replaceable", field);
+ goto nonrepl;
+ }
if (!replaceable_type_p (type))
- goto nonrepl;
+ {
+ if (explain)
+ inform (DECL_SOURCE_LOCATION (field),
+ "field %qD of type %qT is not replaceable", field, type);
+ goto nonrepl;
+ }
}
+ if (!CLASSTYPE_REPLACEABLE_BIT (t)
+ && !union_with_no_declared_special_member_fns (t)
+ && !default_movable_type_p (t))
+ {
+ if (explain)
+ {
+ /* At this point CLASSTYPE_REPLACEABLE_BIT will only
+ be cleared if we failed a condition above. */
+ if (TREE_CODE (t) == UNION_TYPE)
+ inform (location_of (t),
+ "%qT is not marked %<replaceable_if_eligible%>, "
+ "has user-declared special member functions, "
+ "and is not default-movable because:", t);
+ else
+ inform (location_of (t),
+ "%qT is not marked %<replaceable_if_eligible%>, "
+ "and is not default-movable because:", t);
+ auto_diagnostic_nesting_level adnl;
+ default_movable_type_p (t, /*explain=*/true);
+ }
+ goto nonrepl;
+ }
+
+ if (explain)
+ gcc_checking_assert (CLASSTYPE_REPLACEABLE_BIT (t));
CLASSTYPE_REPLACEABLE_BIT (t) = 1;
CLASSTYPE_REPLACEABLE_COMPUTED (t) = 1;
return true;
diff --git a/gcc/testsuite/g++.dg/cpp26/trivially-relocatable12.C
b/gcc/testsuite/g++.dg/cpp26/trivially-relocatable12.C
new file mode 100644
index 00000000000..b9a3d26089a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/trivially-relocatable12.C
@@ -0,0 +1,54 @@
+// { dg-do compile { target c++26 } }
+// Make sure we properly do overload resolution when checking default-movable.
+
+template <typename T>
+constexpr bool trivially_relocatable = __builtin_is_trivially_relocatable(T);
+
+template <typename T>
+constexpr bool replaceable = __builtin_is_replaceable(T);
+
+struct A {
+ A(A&&, int = 0) = delete;
+ A(A&&) = default;
+};
+static_assert(!trivially_relocatable<A>);
+static_assert(!replaceable<A>);
+
+struct B {
+ B& operator=(B&&) = default;
+ template <typename = void> B& operator=(B&&);
+};
+static_assert(!trivially_relocatable<B>);
+static_assert(!replaceable<B>);
+
+struct C {
+ C(C&&, int = 0) = delete;
+ C(C&&);
+};
+static_assert(!trivially_relocatable<C>);
+static_assert(!replaceable<C>);
+
+struct D {
+ D(D&&) = default;
+ // lvalue operator= is fine
+ D& operator=(D&&) & = default;
+};
+static_assert(trivially_relocatable<D>);
+static_assert(replaceable<D>);
+
+struct E {
+ E(E&&) = default;
+ // rvalue operator= is not fine
+ E& operator=(E&&) && = default;
+};
+static_assert(!trivially_relocatable<E>);
+static_assert(!replaceable<E>);
+
+struct F {
+ F(F&&) = default;
+ // having both is fine as long as we select a defaulted one.
+ F& operator=(const F&) & = default;
+ F& operator=(F&) &&;
+};
+static_assert(trivially_relocatable<F>);
+static_assert(replaceable<F>);
diff --git a/gcc/testsuite/g++.dg/cpp26/trivially-relocatable13.C
b/gcc/testsuite/g++.dg/cpp26/trivially-relocatable13.C
new file mode 100644
index 00000000000..0b8f7c15132
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/trivially-relocatable13.C
@@ -0,0 +1,106 @@
+// { dg-do compile { target c++26 } }
+
+template <typename T>
+constexpr bool relocatable = __builtin_is_trivially_relocatable(T);
+
+static_assert(relocatable<void()>); // { dg-error "assert" }
+// { dg-message "is not trivially relocatable, because" "" { target *-*-* }
.-1 }
+// { dg-message "not a class or scalar type" "" { target *-*-* } .-2 }
+
+
+template <typename T>
+constexpr bool nothrow_relocatable = __builtin_is_nothrow_relocatable(T);
+
+static_assert(nothrow_relocatable<void()>); // { dg-line nrv }
+// { dg-error "assert" "" { target *-*-* } nrv }
+// { dg-message "is not nothrow relocatable, because" "" { target *-*-* } nrv }
+// { dg-message "not trivially relocatable" "" { target *-*-* } nrv }
+// { dg-message "not a class or scalar type" "" { target *-*-* } nrv }
+// { dg-message "not nothrow move constructible" "" { target *-*-* } nrv }
+// { dg-error "could not convert" "" { target *-*-* } nrv }
+
+
+template <typename T>
+constexpr bool replaceable = __builtin_is_replaceable(T);
+
+static_assert(replaceable<void()>); // { dg-error "assert" }
+// { dg-message "is not replaceable, because" "" { target *-*-* } .-1 }
+// { dg-message "not a class or scalar type" "" { target *-*-* } .-2 }
+
+static_assert(replaceable<const int>); // { dg-error "assert" }
+// { dg-message "const" "" { target *-*-* } .-1 }
+
+
+struct A {};
+struct B : virtual A {}; // { dg-message "virtual base" }
+static_assert(relocatable<B>); // { dg-error "assert" }
+
+struct C {
+ ~C() = delete; // { dg-message "deleted|declared here" }
+};
+static_assert(relocatable<C>); // { dg-error "assert" }
+
+struct D { // { dg-error "deleted" }
+ C c;
+};
+static_assert(relocatable<D>); // { dg-error "assert" }
+
+struct E trivially_relocatable_if_eligible { // { dg-bogus
"trivially_relocatable_if_eligible" }
+ B b; // { dg-message "field" }
+};
+static_assert(relocatable<E>); // { dg-error "assert" }
+
+struct F : E {}; // { dg-message "base class" }
+static_assert(relocatable<F>); // { dg-error "assert" }
+
+struct G { // { dg-message "default-movable" }
+ G(G&&); // { dg-message "user-provided" }
+};
+static_assert(relocatable<G>); // { dg-error "assert" }
+
+union H { // { dg-message "user-declared special member functions" }
+ H(H&&) = default;
+ H& operator=(H&&); // { dg-message "user-provided" }
+};
+static_assert(relocatable<H[2]>); // { dg-error "assert" }
+
+struct I { // { dg-message "default-movable" }
+ ~I(); // { dg-message "user-provided" }
+};
+static_assert(relocatable<I>); // { dg-error "assert" }
+
+struct J { // { dg-message "default-movable" }
+ ~J() noexcept(false); // { dg-message "noexcept" }
+};
+static_assert(nothrow_relocatable<J[5]>); // { dg-error "assert" }
+
+struct K { // { dg-message "default-movable" }
+ ~K();
+};
+static_assert(replaceable<K>); // { dg-error "assert" }
+
+struct L {
+ ~L() = delete; // { dg-message "deleted" }
+};
+static_assert(replaceable<L>); // { dg-error "assert" }
+
+struct M {
+ M(M&&, int = 0);
+ M(M&&);
+};
+static_assert(replaceable<M>); // { dg-error "assert" }
+// { dg-error "ambiguous" "" { target *-*-* } .-1 }
+
+struct N {
+ N(N&&); // { dg-message "user-provided" }
+ N& operator=(N&&);
+};
+static_assert(replaceable<N>); // { dg-error "assert" }
+
+struct O replaceable_if_eligible { // { dg-bogus "replaceable_if_eligible" }
+ N n; // { dg-message "field" }
+};
+static_assert(replaceable<O>); // { dg-error "assert" }
+
+struct P : O {}; // { dg-message "base" }
+static_assert(replaceable<P[2]>); // { dg-error "assert" }
--
2.51.0