aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwaffl3x <waffl3x@protonmail.com>2024-01-07 23:10:00 +0000
committerJason Merrill <jason@redhat.com>2024-01-09 15:59:04 -0500
commitbfad006b88ec26e91b7edf9cf9ad4aaf9b8a9727 (patch)
treec69ac0ecb1af40a83972fb337c4a62229e01171e
parent07d09f0af100a9873982fba663800d87bfd73585 (diff)
downloadgcc-bfad006b88ec26e91b7edf9cf9ad4aaf9b8a9727.zip
gcc-bfad006b88ec26e91b7edf9cf9ad4aaf9b8a9727.tar.gz
gcc-bfad006b88ec26e91b7edf9cf9ad4aaf9b8a9727.tar.bz2
c++: P0847R7 (deducing this) - CWG2586 [PR102609]
This adds support for defaulted comparison operators and copy/move assignment operators, as well as allowing user defined xobj copy/move assignment operators. It turns out defaulted comparison operators already worked though, so this just adds a test for them. Defaulted comparison operators were not so nice and required a bit of a hack. Should work fine though! The diagnostics leave something to be desired, and there are some things that could be improved with more extensive design changes. There are a few notes left indicating where I think we could make improvements. Aside from some small bugs, with this commit xobj member functions should be feature complete. PR c++/102609 gcc/cp/ChangeLog: PR c++/102609 C++23 P0847R7 (deducing this) - CWG2586. * decl.cc (copy_fn_p): Accept xobj copy assignment functions. (move_signature_fn_p): Accept xobj move assignment functions. * method.cc (do_build_copy_assign): Handle defaulted xobj member functions. (defaulted_late_check): Comment. (defaultable_fn_check): Comment. gcc/testsuite/ChangeLog: PR c++/102609 C++23 P0847R7 (deducing this) - CWG2586. * g++.dg/cpp23/explicit-obj-basic6.C: New test. * g++.dg/cpp23/explicit-obj-default1.C: New test. * g++.dg/cpp23/explicit-obj-default2.C: New test. Signed-off-by: Waffl3x <waffl3x@protonmail.com>
-rw-r--r--gcc/cp/decl.cc28
-rw-r--r--gcc/cp/method.cc55
-rw-r--r--gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C51
-rw-r--r--gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C57
-rw-r--r--gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C65
5 files changed, 248 insertions, 8 deletions
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 7f26705..b10a72a 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -15663,7 +15663,19 @@ copy_fn_p (const_tree d)
&& DECL_NAME (d) != assign_op_identifier)
return 0;
- args = FUNCTION_FIRST_USER_PARMTYPE (d);
+ if (DECL_XOBJ_MEMBER_FUNCTION_P (d))
+ {
+ tree object_param = TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (d)));
+ if (!TYPE_REF_P (object_param)
+ || TYPE_REF_IS_RVALUE (object_param)
+ /* Reject unrelated object parameters. */
+ || TYPE_MAIN_VARIANT (TREE_TYPE (object_param)) != DECL_CONTEXT (d)
+ || CP_TYPE_CONST_P (TREE_TYPE (object_param)))
+ return 0;
+ args = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (d)));
+ }
+ else
+ args = FUNCTION_FIRST_USER_PARMTYPE (d);
if (!args)
return 0;
@@ -15738,7 +15750,19 @@ move_signature_fn_p (const_tree d)
&& DECL_NAME (d) != assign_op_identifier)
return 0;
- args = FUNCTION_FIRST_USER_PARMTYPE (d);
+ if (DECL_XOBJ_MEMBER_FUNCTION_P (d))
+ {
+ tree object_param = TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (d)));
+ if (!TYPE_REF_P (object_param)
+ || TYPE_REF_IS_RVALUE (object_param)
+ /* Reject unrelated object parameters. */
+ || TYPE_MAIN_VARIANT (TREE_TYPE (object_param)) != DECL_CONTEXT (d)
+ || CP_TYPE_CONST_P (TREE_TYPE (object_param)))
+ return 0;
+ args = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (d)));
+ }
+ else
+ args = FUNCTION_FIRST_USER_PARMTYPE (d);
if (!args)
return 0;
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index aa5a044..da6a08a 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -795,13 +795,19 @@ do_build_copy_assign (tree fndecl)
compound_stmt = begin_compound_stmt (0);
parm = convert_from_reference (parm);
+ /* If we are building a defaulted xobj copy/move assignment operator then
+ current_class_ref will not have been set up.
+ Kind of an icky hack, but what can ya do? */
+ tree const class_ref = DECL_XOBJ_MEMBER_FUNCTION_P (fndecl)
+ ? cp_build_fold_indirect_ref (DECL_ARGUMENTS (fndecl)) : current_class_ref;
+
if (trivial
&& is_empty_class (current_class_type))
/* Don't copy the padding byte; it might not have been allocated
if *this is a base subobject. */;
else if (trivial)
{
- tree t = build2 (MODIFY_EXPR, void_type_node, current_class_ref, parm);
+ tree t = build2 (MODIFY_EXPR, void_type_node, class_ref, parm);
finish_expr_stmt (t);
}
else
@@ -826,7 +832,7 @@ do_build_copy_assign (tree fndecl)
/* Call the base class assignment operator. */
releasing_vec parmvec (make_tree_vector_single (converted_parm));
finish_expr_stmt
- (build_special_member_call (current_class_ref,
+ (build_special_member_call (class_ref,
assign_op_identifier,
&parmvec,
base_binfo,
@@ -839,7 +845,7 @@ do_build_copy_assign (tree fndecl)
fields;
fields = DECL_CHAIN (fields))
{
- tree comp = current_class_ref;
+ tree comp = class_ref;
tree init = parm;
tree field = fields;
tree expr_type;
@@ -898,7 +904,7 @@ do_build_copy_assign (tree fndecl)
finish_expr_stmt (init);
}
}
- finish_return_stmt (current_class_ref);
+ finish_return_stmt (class_ref);
finish_compound_stmt (compound_stmt);
}
@@ -3380,13 +3386,46 @@ defaulted_late_check (tree fn)
NULL, NULL);
tree eh_spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (implicit_fn));
+ /* Includes special handling for a default xobj operator. */
+ auto compare_fn_params = [](tree fn, tree implicit_fn){
+ tree fn_parms = TYPE_ARG_TYPES (TREE_TYPE (fn));
+ tree implicit_fn_parms = TYPE_ARG_TYPES (TREE_TYPE (implicit_fn));
+
+ if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+ {
+ tree fn_obj_ref_type = TREE_VALUE (fn_parms);
+ /* We can't default xobj operators with an xobj parameter that is not
+ an lvalue reference. */
+ if (!TYPE_REF_P (fn_obj_ref_type)
+ || TYPE_REF_IS_RVALUE (fn_obj_ref_type))
+ return false;
+ /* If implicit_fn's object parameter is not a pointer, something is not
+ right. (Or we have finally changed the type of the iobj parameter
+ in iobj member functions.) */
+ gcc_assert (TYPE_PTR_P (TREE_VALUE (implicit_fn_parms)));
+ /* Strip the reference/pointer off each object parameter before
+ comparing them. */
+ if (!same_type_p (TREE_TYPE (fn_obj_ref_type),
+ TREE_TYPE (TREE_VALUE (implicit_fn_parms))))
+ return false;
+ /* We just compared the object parameters, skip over them before
+ passing to compparms. */
+ fn_parms = TREE_CHAIN (fn_parms);
+ implicit_fn_parms = TREE_CHAIN (implicit_fn_parms);
+ }
+ return compparms(fn_parms, implicit_fn_parms);
+ };
+
if (!same_type_p (TREE_TYPE (TREE_TYPE (fn)),
TREE_TYPE (TREE_TYPE (implicit_fn)))
- || !compparms (TYPE_ARG_TYPES (TREE_TYPE (fn)),
- TYPE_ARG_TYPES (TREE_TYPE (implicit_fn))))
+ || !compare_fn_params (fn, implicit_fn))
{
error ("defaulted declaration %q+D does not match the "
"expected signature", fn);
+ /* FIXME: If the user is defaulting an xobj member function we should
+ emit an xobj member function for a signature. When we do this, maybe
+ we can just synthesize implicit_fn as an xobj member function and
+ avoid the dance in compare_fn_parms. */
inform (DECL_SOURCE_LOCATION (fn),
"expected signature: %qD", implicit_fn);
}
@@ -3473,6 +3512,10 @@ defaultable_fn_check (tree fn)
return false;
}
+ /* FIXME: We need to check for xobj member functions here to give better
+ diagnostics for weird cases where unrelated xobj parameters are given.
+ We just want to do better than 'cannot be defaulted'. */
+
if (kind == sfk_none)
{
error ("%qD cannot be defaulted", fn);
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C
new file mode 100644
index 0000000..6b8881e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C
@@ -0,0 +1,51 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// user defined copy/move assignment operators
+
+inline constexpr int add_when_copy = 5;
+inline constexpr int add_when_move = 10;
+inline constexpr int poison = -1;
+
+struct S {
+ int _v;
+ S& operator=(this S& self, S const& rhs) {
+ self._v = rhs._v + add_when_copy;
+ return self;
+ };
+ S& operator=(this S& self, S&& rhs) {
+ self._v = rhs._v + add_when_move;
+ rhs._v = poison;
+ return self;
+ }
+};
+
+inline constexpr int init_val = 5;
+
+int main()
+{
+ S s0{0};
+ S s1{init_val};
+
+ // Sanity check.
+ if (s0._v != 0
+ || s1._v != init_val)
+ __builtin_abort ();
+
+ s0 = s1;
+ if (s0._v != init_val + add_when_copy)
+ __builtin_abort ();
+ if (s1._v != init_val)
+ __builtin_abort ();
+
+ s0 = S{init_val};
+ if (s0._v != init_val + add_when_move)
+ __builtin_abort ();
+
+ S s2{init_val};
+ s0 = static_cast<S&&>(s2);
+ if (s0._v != init_val + add_when_move)
+ __builtin_abort ();
+ if (s2._v != poison)
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C
new file mode 100644
index 0000000..fac3c28
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C
@@ -0,0 +1,57 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// defaulted comparison operators
+
+#include <compare>
+
+struct S {
+ int _v;
+ bool operator==(this S const&, S const&) = default;
+ auto operator<=>(this S const&, S const&) = default;
+};
+
+int main()
+{
+ S const a_10{10};
+ S const b_10{10};
+ S const c_20{20};
+ S const d_5{5};
+
+ if (a_10 != b_10)
+ __builtin_abort ();
+ if (c_20 == a_10)
+ __builtin_abort ();
+ if (!(a_10 == b_10))
+ __builtin_abort ();
+ if (!(c_20 != a_10))
+ __builtin_abort ();
+
+ if (a_10 < b_10)
+ __builtin_abort ();
+ if (a_10 > b_10)
+ __builtin_abort ();
+ if (!(a_10 <= b_10))
+ __builtin_abort ();
+ if (!(a_10 >= b_10))
+ __builtin_abort ();
+
+ if (!(a_10 < c_20))
+ __builtin_abort ();
+ if (a_10 > c_20)
+ __builtin_abort ();
+ if (!(a_10 <= c_20))
+ __builtin_abort ();
+ if (a_10 >= c_20)
+ __builtin_abort ();
+
+ if (a_10 < d_5)
+ __builtin_abort ();
+ if (!(a_10 > d_5))
+ __builtin_abort ();
+ if (a_10 <= d_5)
+ __builtin_abort ();
+ if (!(a_10 >= d_5))
+ __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C
new file mode 100644
index 0000000..786f0c2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C
@@ -0,0 +1,65 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// defaulted copy/move assignment operators
+
+inline constexpr int add_when_copy = 5;
+inline constexpr int add_when_move = 10;
+inline constexpr int poison = -1;
+
+struct A {
+ int _v;
+ A(int v) : _v(v) {}
+ A& operator=(A const& rhs) {
+ if (&rhs == this)
+ return *this;
+ _v = rhs._v + add_when_copy;
+ return *this;
+ }
+ A& operator=(A&& rhs) {
+ if (&rhs == this)
+ return *this;
+ _v = rhs._v + add_when_move;
+ rhs._v = poison;
+ return *this;
+ }
+};
+
+struct S {
+ A _a;
+ S& operator=(this S&, S const&) = default;
+ S& operator=(this S&, S&&) = default;
+
+ int v() const { return _a._v; }
+};
+
+inline constexpr int init_val = 5;
+
+int main()
+{
+ S s0{0};
+ S s1{init_val};
+
+ // Sanity check.
+ if (s0.v () != 0
+ || s1.v () != init_val)
+ __builtin_abort ();
+
+ s0 = s1;
+ if (s0.v () != init_val + add_when_copy)
+ __builtin_abort ();
+ if (s1.v () != init_val)
+ __builtin_abort ();
+
+ s0 = S{init_val};
+ if (s0.v () != init_val + add_when_move)
+ __builtin_abort ();
+
+ S s2{init_val};
+ s0 = static_cast<S&&>(s2);
+ if (s0.v () != init_val + add_when_move)
+ __builtin_abort ();
+ if (s2.v () != poison)
+ __builtin_abort ();
+}
+