diff options
author | waffl3x <waffl3x@protonmail.com> | 2024-01-07 23:10:00 +0000 |
---|---|---|
committer | Jason Merrill <jason@redhat.com> | 2024-01-09 15:59:04 -0500 |
commit | bfad006b88ec26e91b7edf9cf9ad4aaf9b8a9727 (patch) | |
tree | c69ac0ecb1af40a83972fb337c4a62229e01171e /gcc | |
parent | 07d09f0af100a9873982fba663800d87bfd73585 (diff) | |
download | gcc-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>
Diffstat (limited to 'gcc')
-rw-r--r-- | gcc/cp/decl.cc | 28 | ||||
-rw-r--r-- | gcc/cp/method.cc | 55 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C | 51 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C | 57 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C | 65 |
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 (); +} + |