Skip to content

Commit

Permalink
c++: P0847R7 (deducing this) - CWG2586 [PR102609]
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Waffl3x authored and Liaoshihua committed Mar 13, 2024
1 parent 42b8b69 commit 52976a5
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 8 deletions.
28 changes: 26 additions & 2 deletions gcc/cp/decl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15661,7 +15661,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;

Expand Down Expand Up @@ -15736,7 +15748,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;

Expand Down
55 changes: 49 additions & 6 deletions gcc/cp/method.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
51 changes: 51 additions & 0 deletions gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C
Original file line number Diff line number Diff line change
@@ -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 ();
}
57 changes: 57 additions & 0 deletions gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C
Original file line number Diff line number Diff line change
@@ -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 ();
}

65 changes: 65 additions & 0 deletions gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C
Original file line number Diff line number Diff line change
@@ -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 ();
}

0 comments on commit 52976a5

Please sign in to comment.