From: Jason Merrill <jason@redhat.com>
To: Marek Polacek <polacek@redhat.com>
Cc: GCC Patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATCH v2] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
Date: Mon, 26 Sep 2022 13:29:35 -0400 [thread overview]
Message-ID: <548cdcb9-abed-ef66-0dfd-5264cfa4ff3c@redhat.com> (raw)
In-Reply-To: <YyoES0Grh4hTLbks@redhat.com>
On 9/20/22 14:19, Marek Polacek wrote:
> On Tue, Sep 06, 2022 at 10:38:12PM -0400, Jason Merrill wrote:
>> On 9/3/22 12:42, Marek Polacek wrote:
>>> This patch implements https://wg21.link/p2266, which, once again,
>>> changes the implicit move rules. Here's a brief summary of various
>>> changes in this area:
>>>
>>> r125211: Introduced moving from certain lvalues when returning them
>>> r171071: CWG 1148, enable move from value parameter on return
>>> r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
>>> r251035: CWG 1579, do maybe-rvalue overload resolution twice
>>> r11-2411: Avoid calling const copy ctor on implicit move
>>> r11-2412: C++20 implicit move changes, remove the fallback overload
>>> resolution, allow move on throw of parameters and implicit
>>> move of rvalue references
>>>
>>> P2266 enables the implicit move for functions that return references. This
>>> was a one-line change: check TYPE_REF_P. That is, we will now perform
>>> a move in
>>>
>>> X&& foo (X&& x) {
>>> return x;
>>> }
>>>
>>> P2266 also removes the fallback overload resolution, but this was
>>> resolved by r11-2412: we only do convert_for_initialization with
>>> LOOKUP_PREFER_RVALUE in C++17 and older.
>>
>> I wonder if we want to extend the current C++20 handling to the older modes
>> for GCC 13? Not in this patch, but as a followup.
>
> Yes, I think that would be very nice if we removed that code.
>
>>> P2266 also says that a returned move-eligible id-expression is always an
>>> xvalue. This required some further short, but nontrivial changes,
>>> especially when it comes to deduction, because we have to pay attention
>>> to whether we have auto, auto&& (which is like T&&), or decltype(auto)
>>> with (un)parenthesized argument. In C++23,
>>>
>>> decltype(auto) f(int&& x) { return (x); }
>>> auto&& f(int x) { return x; }
>>>
>>> both should deduce to 'int&&' but
>>>
>>> decltype(auto) f(int x) { return x; }
>>>
>>> should deduce to 'int'. A cornucopia of tests attached. I've also
>>> verified that we behave like clang++.
>>>
>>> xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
>>> it cannot use '==' when checking for clk_rvalueref.
>>>
>>> Since this change breaks code, it's only enabled in C++23. In
>>> particular, this code will not compile in C++23:
>>>
>>> int& g(int&& x) { return x; }
>>
>> Nice that the C++20 compatibility is so simple!
>>
>>> because x is now treated as an rvalue, and you can't bind a non-const lvalue
>>> reference to an rvalue.
>>>
>>> There's one FIXME in elision1.C:five, which we should compile but reject
>>> with "passing 'Mutt' as 'this' argument discards qualifiers". That
>>> looks bogus to me, I think I'll open a PR for it.
>>
>> Let's fix that now, I think.
>
> OK, copypasting this bit from the other email so that we can have one
> thread:
>
>> Can of worms. The test is
>>
>> struct Mutt {
>> operator int*() &&;
>> };
>>
>> int* five(Mutt x) {
>> return x; // OK since C++20 because P1155
>> }
>>
>> 'x' should be treated as an rvalue, therefore the operator fn taking
>> an rvalue ref to Mutt should be used to convert 'x' to int*. We fail
>> because we don't treat 'x' as an rvalue because the function doesn't
>> return a class. So the patch should be just
>>
>> --- a/gcc/cp/typeck.cc
>> +++ b/gcc/cp/typeck.cc
>> @@ -10875,10 +10875,7 @@ check_return_expr (tree retval, bool *no_warning)
>> Note that these conditions are similar to, but not as strict as,
>> the conditions for the named return value optimization. */
>> bool converted = false;
>> - tree moved;
>> - /* This is only interesting for class type. */
>> - if (CLASS_TYPE_P (functype)
>> - && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
>> + if (tree moved = treat_lvalue_as_rvalue_p (retval, /*return*/true))
>> {
>> if (cxx_dialect < cxx20)
>> {
>>
>> which fixes the test, but breaks a lot of middle-end warnings. For instance
>> g++.dg/warn/nonnull3.C, where the patch above changes .gimple:
>>
>> bool A::foo<B> (struct A * const this, <<< Unknown tree: offset_type >>> p)
>> {
>> - bool D.2146;
>> + bool D.2150;
>>
>> - D.2146 = p != -1;
>> - return D.2146;
>> + p.0_1 = p;
>> + D.2150 = p.0_1 != -1;
>> + return D.2150;
>> }
>>
>> and we no longer get the warning. I thought maybe I could undo the implicit
>> rvalue conversion in cp_fold, when it sees implicit_rvalue_p, but that didn't
>> work. So currently I'm stuck. Should we try to figure this out or push aside?
>
>> Can you undo the implicit rvalue conversion within check_return_expr,
>> where we can still refer back to the original expression?
>
> Unfortunately no, one problem is that treat_lvalue_as_rvalue_p modifies
> the underlying decl by setting TREE_ADDRESSABLE, which then presumably
> breaks warnings. That is, treat_ can get 'VCE<X>(x)' and produce
> '*NLE<(X&) &x>' where 'x' flags have been modified, since we're taking
> x's address.
>
>> Or avoid the rvalue conversion if the return type is scalar?
>
> I wish :(. In the 'five' example above, the return type is a pointer,
> a scalar, but we have to convert to rvalue.
OK, then when both the return type and the type of the return value are
scalar?
> It's sort of sad that this corner case causes so much trouble: I think
> we have to do the conversion only because of ref-qualifiers, so that
> the correct operator function is chosen.
>
> A way out may be setting a flag on the V_C_E that indicates it is an
> rvalue, rather than performing the conversion above. This was your
> idea so I don't want to take credit for it. Should I go ahead and
> try it?
Sure, probably in build_static_cast_1.
>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>>
>>> PR c++/101165
>>>
>>> gcc/c-family/ChangeLog:
>>>
>>> * c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> * call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
>>> * cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
>>> * pt.cc (unparenthesized_id_or_class_member_access_p): New function,
>>> broken out of...
>>> (do_auto_deduction): ...here. Use it.
>>> * tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
>>> * typeck.cc (check_return_expr): In C++23, maybe call
>>> treat_lvalue_as_rvalue_p before do_auto_deduction. Allow implicit
>>> move for functions returning a reference as well.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> * g++.dg/conversion/pr41426.C: Add dg-error for C++23.
>>> * g++.dg/cpp0x/elision_weak.C: Likewise.
>>> * g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
>>> * g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
>>> * g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
>>> * g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
>>> * g++.dg/gomp/pr56217.C: Only compile in c++20_down.
>>> * g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
>>> * g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
>>> * g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
>>> * g++.old-deja/g++.jason/temporary2.C: Likewise.
>>> * g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
>>> * g++.dg/cpp1y/decltype-auto6.C: New test.
>>> * g++.dg/cpp23/decltype1.C: New test.
>>> * g++.dg/cpp23/decltype2.C: New test.
>>> * g++.dg/cpp23/elision1.C: New test.
>>> * g++.dg/cpp23/elision2.C: New test.
>>> * g++.dg/cpp23/elision3.C: New test.
>>> * g++.dg/cpp23/elision4.C: New test.
>>> * g++.dg/cpp23/elision5.C: New test.
>>> * g++.dg/cpp23/elision6.C: New test.
>>> * g++.dg/cpp23/elision7.C: New test.
>>> ---
>>> gcc/c-family/c-cppbuiltin.cc | 1 +
>>> gcc/cp/call.cc | 2 +-
>>> gcc/cp/cp-tree.h | 1 +
>>> gcc/cp/pt.cc | 33 +++--
>>> gcc/cp/tree.cc | 2 +-
>>> gcc/cp/typeck.cc | 28 ++++-
>>> gcc/testsuite/g++.dg/conversion/pr41426.C | 10 +-
>>> gcc/testsuite/g++.dg/cpp0x/elision_weak.C | 4 +-
>>> gcc/testsuite/g++.dg/cpp0x/move-return3.C | 3 +-
>>> gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C | 2 +-
>>> gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C | 19 +++
>>> .../g++.dg/cpp1y/lambda-generic-89419.C | 6 +-
>>> gcc/testsuite/g++.dg/cpp23/decltype1.C | 113 ++++++++++++++++++
>>> gcc/testsuite/g++.dg/cpp23/decltype2.C | 49 ++++++++
>>> gcc/testsuite/g++.dg/cpp23/elision1.C | 109 +++++++++++++++++
>>> gcc/testsuite/g++.dg/cpp23/elision2.C | 46 +++++++
>>> gcc/testsuite/g++.dg/cpp23/elision3.C | 16 +++
>>> gcc/testsuite/g++.dg/cpp23/elision4.C | 38 ++++++
>>> gcc/testsuite/g++.dg/cpp23/elision5.C | 53 ++++++++
>>> gcc/testsuite/g++.dg/cpp23/elision6.C | 20 ++++
>>> gcc/testsuite/g++.dg/cpp23/elision7.C | 72 +++++++++++
>>> gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C | 6 +
>>> gcc/testsuite/g++.dg/gomp/pr56217.C | 4 +-
>>> .../g++.dg/warn/Wno-return-local-addr.C | 2 +-
>>> .../g++.dg/warn/Wreturn-local-addr.C | 2 +-
>>> .../g++.old-deja/g++.brendan/crash55.C | 3 +-
>>> .../g++.old-deja/g++.jason/temporary2.C | 2 +-
>>> gcc/testsuite/g++.old-deja/g++.mike/p2846b.C | 4 +-
>>> 28 files changed, 613 insertions(+), 37 deletions(-)
>>> create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
>>> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C
>>>
>>> diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
>>> index a1557eb23d5..dd8b20d0c34 100644
>>> --- a/gcc/c-family/c-cppbuiltin.cc
>>> +++ b/gcc/c-family/c-cppbuiltin.cc
>>> @@ -1081,6 +1081,7 @@ c_cpp_builtins (cpp_reader *pfile)
>>> cpp_define (pfile, "__cpp_constexpr=202110L");
>>> cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
>>> cpp_define (pfile, "__cpp_named_character_escapes=202207L");
>>> + cpp_define (pfile, "__cpp_implicit_move=202207L");
>>> }
>>> if (flag_concepts)
>>> {
>>> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
>>> index d107a2814dc..cb4ae9ecf9d 100644
>>> --- a/gcc/cp/call.cc
>>> +++ b/gcc/cp/call.cc
>>> @@ -1865,7 +1865,7 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
>>> /* Nor the reverse. */
>>> if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
>>> /* Unless it's really an lvalue. */
>>
>> Please add a bit about C++20 vs C++23 in this comment.
>
> Done.
>
>>> - && !(cxx_dialect >= cxx20
>>> + && !(cxx_dialect == cxx20
>>> && (gl_kind & clk_implicit_rval))
>>> && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
>>> || (flags & LOOKUP_NO_RVAL_BIND))
>>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>>> index 7b28405c3ac..4cdbbc34cb3 100644
>>> --- a/gcc/cp/cp-tree.h
>>> +++ b/gcc/cp/cp-tree.h
>>> @@ -7278,6 +7278,7 @@ extern tree make_constrained_decltype_auto (tree, tree);
>>> extern tree make_template_placeholder (tree);
>>> extern bool template_placeholder_p (tree);
>>> extern bool ctad_template_p (tree);
>>> +extern bool unparenthesized_id_or_class_member_access_p (tree);
>>> extern tree do_auto_deduction (tree, tree, tree,
>>> tsubst_flags_t
>>> = tf_warning_or_error,
>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>> index cd0d8920ed0..b347a9a78fc 100644
>>> --- a/gcc/cp/pt.cc
>>> +++ b/gcc/cp/pt.cc
>>> @@ -30405,6 +30405,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
>>> cp_type_quals (ptype));
>>> }
>>> +/* Return true if INIT is an unparenthesized id-expression or an
>>> + unparenthesized class member access. Used for the argument of
>>> + decltype(auto). */
>>> +
>>> +bool
>>> +unparenthesized_id_or_class_member_access_p (tree init)
>>> +{
>>> + STRIP_ANY_LOCATION_WRAPPER (init);
>>> +
>>
>> I wonder about looking through IMPLICIT_RVALUE_P here...
>
> Unwrapping an IMPLICIT_RVALUE_P is really ugly, and teaching
> unparenthesized_ about IMPLICIT_RVALUE_P wouldn't be enough,
> since we'd have to unwrap INIT as well so that finish_decltype_type
> gets the decl, not '*x'. However, I have...
>
>>> + /* We need to be able to tell '(r)' and 'r' apart (when it's of
>>> + reference type). Only the latter is an id-expression. */
>>> + if (REFERENCE_REF_P (init)
>>> + && !REF_PARENTHESIZED_P (init))
>>> + init = TREE_OPERAND (init, 0);
>>> + return (DECL_P (init)
>>> + || ((TREE_CODE (init) == COMPONENT_REF
>>> + || TREE_CODE (init) == SCOPE_REF)
>>> + && !REF_PARENTHESIZED_P (init)));
>>> +}
>>> +
>>> /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
>>> from INIT. AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
>>> The CONTEXT determines the context in which auto deduction is performed
>>> @@ -30501,18 +30521,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>>> }
>>> else if (AUTO_IS_DECLTYPE (auto_node))
>>> {
>>> - /* Figure out if INIT is an unparenthesized id-expression or an
>>> - unparenthesized class member access. */
>>> - tree stripped_init = tree_strip_any_location_wrapper (init);
>>> - /* We need to be able to tell '(r)' and 'r' apart (when it's of
>>> - reference type). Only the latter is an id-expression. */
>>> - if (REFERENCE_REF_P (stripped_init)
>>> - && !REF_PARENTHESIZED_P (stripped_init))
>>> - stripped_init = TREE_OPERAND (stripped_init, 0);
>>> - const bool id = (DECL_P (stripped_init)
>>> - || ((TREE_CODE (stripped_init) == COMPONENT_REF
>>> - || TREE_CODE (stripped_init) == SCOPE_REF)
>>> - && !REF_PARENTHESIZED_P (stripped_init)));
>>> + const bool id = unparenthesized_id_or_class_member_access_p (init);
>>> tree deduced = finish_decltype_type (init, id, complain);
>>> deduced = canonicalize_type_argument (deduced, complain);
>>> if (deduced == error_mark_node)
>>> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
>>> index c678e3b9c4c..226ffd48519 100644
>>> --- a/gcc/cp/tree.cc
>>> +++ b/gcc/cp/tree.cc
>>> @@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
>>> bool
>>> xvalue_p (const_tree ref)
>>> {
>>> - return (lvalue_kind (ref) == clk_rvalueref);
>>> + return (lvalue_kind (ref) & clk_rvalueref);
>>> }
>>> /* True if REF is a bit-field. */
>>> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
>>> index b99947c10fd..fe88c1a7b84 100644
>>> --- a/gcc/cp/typeck.cc
>>> +++ b/gcc/cp/typeck.cc
>>> @@ -10876,8 +10893,9 @@ check_return_expr (tree retval, bool *no_warning)
>>> the conditions for the named return value optimization. */
>>> bool converted = false;
>>> tree moved;
>>> - /* This is only interesting for class type. */
>>> - if (CLASS_TYPE_P (functype)
>>> + /* Until C++23, this was only interesting for class type. */
>>> + if ((CLASS_TYPE_P (functype)
>>> + || (cxx_dialect >= cxx23 && TYPE_REF_P (functype)))
>>> && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
>>
>> ...and calling treat_lvalue_as_rvalue_p before auto deduction...
>>
>>> {
>>> if (cxx_dialect < cxx20)
>>> @@ -10679,10 +10679,27 @@ check_return_expr (tree retval, bool *no_warning)
>>> }
>>> else
>>> {
>>> - if (!retval)
>>> - retval = void_node;
>>> auto_node = type_uses_auto (pattern);
>>> - type = do_auto_deduction (pattern, retval, auto_node,
>>> + tree r;
>>> + if (!retval)
>>> + r = void_node;
>>> + /* In C++23, we must deduce the type to int&& for code like
>>> + decltype(auto) f(int&& x) { return (x); }
>>> + or
>>> + auto&& f(int x) { return x; }
>>> + so we use treat_lvalue_as_rvalue_p earlier. But don't do it for
>>> + decltype(auto) f(int x) { return x; }
>>> + where we should deduce 'int' rather than 'int&&'; transmogrifying
>>> + RETVAL to an rvalue would break that. */
>>> + else if (cxx_dialect >= cxx23
>>> + && (!AUTO_IS_DECLTYPE (auto_node)
>>> + || !unparenthesized_id_or_class_member_access_p (retval))
>>> + && (r = treat_lvalue_as_rvalue_p
>>> + (maybe_undo_parenthesized_ref (retval), /*return*/true)))
>>> + /* Use it. */;
>>
>> ...so we don't need to do this?
>
> ...moved this into do_auto_deduction, where I check context == adc_return_type.
>
> FWIW this is what I have at this moment.
>
> -- >8 --
> This patch implements https://wg21.link/p2266, which, once again,
> changes the implicit move rules. Here's a brief summary of various
> changes in this area:
>
> r125211: Introduced moving from certain lvalues when returning them
> r171071: CWG 1148, enable move from value parameter on return
> r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
> r251035: CWG 1579, do maybe-rvalue overload resolution twice
> r11-2411: Avoid calling const copy ctor on implicit move
> r11-2412: C++20 implicit move changes, remove the fallback overload
> resolution, allow move on throw of parameters and implicit
> move of rvalue references
>
> P2266 enables the implicit move for functions that return references. This
> was a one-line change: check TYPE_REF_P. That is, we will now perform
> a move in
>
> X&& foo (X&& x) {
> return x;
> }
>
> P2266 also removes the fallback overload resolution, but this was
> resolved by r11-2412: we only do convert_for_initialization with
> LOOKUP_PREFER_RVALUE in C++17 and older.
> P2266 also says that a returned move-eligible id-expression is always an
> xvalue. This required some further short, but nontrivial changes,
> especially when it comes to deduction, because we have to pay attention
> to whether we have auto, auto&& (which is like T&&), or decltype(auto)
> with (un)parenthesized argument. In C++23,
>
> decltype(auto) f(int&& x) { return (x); }
> auto&& f(int x) { return x; }
>
> both should deduce to 'int&&' but
>
> decltype(auto) f(int x) { return x; }
>
> should deduce to 'int'. A cornucopia of tests attached. I've also
> verified that we behave like clang++.
>
> xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
> it cannot use '==' when checking for clk_rvalueref.
>
> Since this change breaks code, it's only enabled in C++23. In
> particular, this code will not compile in C++23:
>
> int& g(int&& x) { return x; }
>
> because x is now treated as an rvalue, and you can't bind a non-const lvalue
> reference to an rvalue.
>
> There's one FIXME in elision1.C:five, which we should compile but reject
> with "passing 'Mutt' as 'this' argument discards qualifiers". This is
> PR106882.
>
> PR c++/101165
>
> gcc/c-family/ChangeLog:
>
> * c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.
>
> gcc/cp/ChangeLog:
>
> * call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
> * cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
> * pt.cc (unparenthesized_id_or_class_member_access_p): New function,
> broken out of...
> (do_auto_deduction): ...here. Use it. In C++23, maybe call
> treat_lvalue_as_rvalue_p.
> * tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
> * typeck.cc (check_return_expr): Allow implicit move for functions
> returning a reference as well.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/conversion/pr41426.C: Add dg-error for C++23.
> * g++.dg/cpp0x/elision_weak.C: Likewise.
> * g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
> * g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
> * g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
> * g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
> * g++.dg/gomp/pr56217.C: Only compile in c++20_down.
> * g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
> * g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
> * g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
> * g++.old-deja/g++.jason/temporary2.C: Likewise.
> * g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
> * g++.dg/cpp1y/decltype-auto6.C: New test.
> * g++.dg/cpp23/decltype1.C: New test.
> * g++.dg/cpp23/decltype2.C: New test.
> * g++.dg/cpp23/elision1.C: New test.
> * g++.dg/cpp23/elision2.C: New test.
> * g++.dg/cpp23/elision3.C: New test.
> * g++.dg/cpp23/elision4.C: New test.
> * g++.dg/cpp23/elision5.C: New test.
> * g++.dg/cpp23/elision6.C: New test.
> * g++.dg/cpp23/elision7.C: New test.
> ---
> gcc/c-family/c-cppbuiltin.cc | 1 +
> gcc/cp/call.cc | 6 +-
> gcc/cp/cp-tree.h | 1 +
> gcc/cp/pt.cc | 50 ++++++--
> gcc/cp/tree.cc | 2 +-
> gcc/cp/typeck.cc | 6 +-
> gcc/testsuite/g++.dg/conversion/pr41426.C | 10 +-
> gcc/testsuite/g++.dg/cpp0x/elision_weak.C | 4 +-
> gcc/testsuite/g++.dg/cpp0x/move-return3.C | 3 +-
> gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C | 2 +-
> gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C | 19 +++
> .../g++.dg/cpp1y/lambda-generic-89419.C | 6 +-
> gcc/testsuite/g++.dg/cpp23/decltype1.C | 113 ++++++++++++++++++
> gcc/testsuite/g++.dg/cpp23/decltype2.C | 49 ++++++++
> gcc/testsuite/g++.dg/cpp23/elision1.C | 109 +++++++++++++++++
> gcc/testsuite/g++.dg/cpp23/elision2.C | 46 +++++++
> gcc/testsuite/g++.dg/cpp23/elision3.C | 16 +++
> gcc/testsuite/g++.dg/cpp23/elision4.C | 38 ++++++
> gcc/testsuite/g++.dg/cpp23/elision5.C | 53 ++++++++
> gcc/testsuite/g++.dg/cpp23/elision6.C | 20 ++++
> gcc/testsuite/g++.dg/cpp23/elision7.C | 72 +++++++++++
> gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C | 6 +
> gcc/testsuite/g++.dg/gomp/pr56217.C | 4 +-
> .../g++.dg/warn/Wno-return-local-addr.C | 2 +-
> .../g++.dg/warn/Wreturn-local-addr.C | 2 +-
> .../g++.old-deja/g++.brendan/crash55.C | 3 +-
> .../g++.old-deja/g++.jason/temporary2.C | 2 +-
> gcc/testsuite/g++.old-deja/g++.mike/p2846b.C | 4 +-
> 28 files changed, 614 insertions(+), 35 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
> create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C
>
> diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
> index a1557eb23d5..dd8b20d0c34 100644
> --- a/gcc/c-family/c-cppbuiltin.cc
> +++ b/gcc/c-family/c-cppbuiltin.cc
> @@ -1081,6 +1081,7 @@ c_cpp_builtins (cpp_reader *pfile)
> cpp_define (pfile, "__cpp_constexpr=202110L");
> cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
> cpp_define (pfile, "__cpp_named_character_escapes=202207L");
> + cpp_define (pfile, "__cpp_implicit_move=202207L");
> }
> if (flag_concepts)
> {
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index 7e9289fc2d0..1a786fc96df 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -1864,8 +1864,10 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
>
> /* Nor the reverse. */
> if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
> - /* Unless it's really an lvalue. */
> - && !(cxx_dialect >= cxx20
> + /* Unless it's really a C++20 lvalue being treated as an xvalue.
> + But in C++23, such an expression is just an xvalue, not a special
> + lvalue, so the binding is once again ill-formed. */
> + && !(cxx_dialect == cxx20
> && (gl_kind & clk_implicit_rval))
> && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
> || (flags & LOOKUP_NO_RVAL_BIND))
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index f19ecafc266..35fea6ac3cf 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7278,6 +7278,7 @@ extern tree make_constrained_decltype_auto (tree, tree);
> extern tree make_template_placeholder (tree);
> extern bool template_placeholder_p (tree);
> extern bool ctad_template_p (tree);
> +extern bool unparenthesized_id_or_class_member_access_p (tree);
> extern tree do_auto_deduction (tree, tree, tree,
> tsubst_flags_t
> = tf_warning_or_error,
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index db4e808adec..5833fd54959 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -30407,6 +30407,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
> cp_type_quals (ptype));
> }
>
> +/* Return true if INIT is an unparenthesized id-expression or an
> + unparenthesized class member access. Used for the argument of
> + decltype(auto). */
> +
> +bool
> +unparenthesized_id_or_class_member_access_p (tree init)
> +{
> + STRIP_ANY_LOCATION_WRAPPER (init);
> +
> + /* We need to be able to tell '(r)' and 'r' apart (when it's of
> + reference type). Only the latter is an id-expression. */
> + if (REFERENCE_REF_P (init)
> + && !REF_PARENTHESIZED_P (init))
> + init = TREE_OPERAND (init, 0);
> + return (DECL_P (init)
> + || ((TREE_CODE (init) == COMPONENT_REF
> + || TREE_CODE (init) == SCOPE_REF)
> + && !REF_PARENTHESIZED_P (init)));
> +}
> +
> /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
> from INIT. AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
> The CONTEXT determines the context in which auto deduction is performed
> @@ -30442,6 +30462,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
> auto_node. */
> complain &= ~tf_partial;
>
> + /* In C++23, we must deduce the type to int&& for code like
> + decltype(auto) f(int&& x) { return (x); }
> + or
> + auto&& f(int x) { return x; }
> + so we use treat_lvalue_as_rvalue_p. But don't do it for
> + decltype(auto) f(int x) { return x; }
> + where we should deduce 'int' rather than 'int&&'; transmogrifying
> + INIT to an rvalue would break that. */
> + tree r;
> + if (cxx_dialect >= cxx23
> + && context == adc_return_type
> + && (!AUTO_IS_DECLTYPE (auto_node)
> + || !unparenthesized_id_or_class_member_access_p (init))
> + && (r = treat_lvalue_as_rvalue_p (maybe_undo_parenthesized_ref (init),
> + /*return*/true)))
> + init = r;
> +
> if (tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (auto_node))
> /* C++17 class template argument deduction. */
> return do_class_deduction (type, tmpl, init, flags, complain);
> @@ -30503,18 +30540,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
> }
> else if (AUTO_IS_DECLTYPE (auto_node))
> {
> - /* Figure out if INIT is an unparenthesized id-expression or an
> - unparenthesized class member access. */
> - tree stripped_init = tree_strip_any_location_wrapper (init);
> - /* We need to be able to tell '(r)' and 'r' apart (when it's of
> - reference type). Only the latter is an id-expression. */
> - if (REFERENCE_REF_P (stripped_init)
> - && !REF_PARENTHESIZED_P (stripped_init))
> - stripped_init = TREE_OPERAND (stripped_init, 0);
> - const bool id = (DECL_P (stripped_init)
> - || ((TREE_CODE (stripped_init) == COMPONENT_REF
> - || TREE_CODE (stripped_init) == SCOPE_REF)
> - && !REF_PARENTHESIZED_P (stripped_init)));
> + const bool id = unparenthesized_id_or_class_member_access_p (init);
> tree deduced = finish_decltype_type (init, id, complain);
> deduced = canonicalize_type_argument (deduced, complain);
> if (deduced == error_mark_node)
> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> index d0bd41ae5a0..ea4dfc651bb 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
> bool
> xvalue_p (const_tree ref)
> {
> - return (lvalue_kind (ref) == clk_rvalueref);
> + return (lvalue_kind (ref) & clk_rvalueref);
> }
>
> /* True if REF is a bit-field. */
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 22d834d3a58..83545ab66ef 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -10872,8 +10872,10 @@ check_return_expr (tree retval, bool *no_warning)
> the conditions for the named return value optimization. */
> bool converted = false;
> tree moved;
> - /* This is only interesting for class type. */
> - if (CLASS_TYPE_P (functype)
> + /* Until C++23, this was only interesting for class type, but
> + (FIXME) in C++23, we should do the below for any type. */
> + if ((CLASS_TYPE_P (functype)
> + || (cxx_dialect >= cxx23 && TYPE_REF_P (functype)))
> && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
> {
> if (cxx_dialect < cxx20)
> diff --git a/gcc/testsuite/g++.dg/conversion/pr41426.C b/gcc/testsuite/g++.dg/conversion/pr41426.C
> index 5493a91ecfa..b4ecbca5f3a 100644
> --- a/gcc/testsuite/g++.dg/conversion/pr41426.C
> +++ b/gcc/testsuite/g++.dg/conversion/pr41426.C
> @@ -11,19 +11,20 @@ struct A
> A<float> g1()
> {
> float f[] = {1.1f, 2.3f};
> - return f;
> + return f; // { dg-error "cannot bind non-const" "" { target c++23 } }
> }
>
> const A<float> &g3()
> {
> float f[] = {1.1f, 2.3f};
> - return f; // { dg-warning "returning reference to temporary" }
> + return f; // { dg-warning "returning reference to temporary" "" { target c++20_down } }
> +// { dg-error "non-const lvalue|invalid user-defined conversion" "" { target c++23 } .-1 }
> }
>
> A<float> &g4()
> {
> float f[] = {1.1f, 2.3f};
> - return f; // { dg-error "cannot bind non-const lvalue ref" }
> + return f; // { dg-error "cannot bind non-const lvalue ref|invalid user-defined conversion" }
> }
>
> struct B
> @@ -35,6 +36,5 @@ struct B
> B g2()
> {
> int c[10];
> - return c;
> + return c; // { dg-error "non-const lvalue" "" { target c++23 } }
> }
> -
> diff --git a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> index e8ba7551d84..ddd12743130 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> @@ -9,11 +9,11 @@ struct S
> S f()
> {
> S s;
> - return s;
> + return s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
> }
>
> void g()
> {
> S s;
> - throw s;
> + throw s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
> }
> diff --git a/gcc/testsuite/g++.dg/cpp0x/move-return3.C b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> index c79f0591936..30a936fb35a 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> @@ -1,6 +1,7 @@
> // PR c++/91212
> // Test that C++11 implicit move semantics don't call the const copy.
> -// { dg-do link }
> +// In C++23, we call #2.
> +// { dg-do link { target c++20_down } }
>
> struct T { int i; };
>
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> index 56e011e36f4..24b32edfacf 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> @@ -8,5 +8,5 @@ auto constexpr RtoL1(T&& r) -> decltype(auto) {
> int main() {
> int t;
> int x{3};
> - decltype (RtoL1(x+0)) y = t;
> + decltype (RtoL1(x+0)) y = t; // { dg-error "cannot bind rvalue reference" "" { target c++23 } }
> }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> new file mode 100644
> index 00000000000..da53278645c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> @@ -0,0 +1,19 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++14 } }
> +// A variant of cxx23/elision1.C:eight, just with ().
> +
> +struct Widget {
> + Widget(Widget&&);
> +};
> +
> +Widget val();
> +
> +decltype(auto)
> +foo ()
> +{
> + decltype(auto) x = val(); // OK, x is Widget
> + // We deduce the return type to int&&, therefore we're doing something
> + // we ought not to be doing -- returning a reference to a local variable!
> + // In C++20, we deduce to int&, but that has the same problem!
> + return (x); // { dg-warning "reference to local variable" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> index 46ce909f3b8..8e64d4e64ab 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> @@ -2,7 +2,7 @@
> // { dg-do compile { target c++14 } }
>
> struct A;
> -struct B {
> +struct B { // { dg-error "cannot bind" "" { target c++23 } }
> struct C { C (); C (C &); } b;
> };
> struct D { A operator* (); };
> @@ -13,12 +13,12 @@ struct E {
> auto bar () { return e; }
> D e;
> };
> -struct F { B f; int g; };
> +struct F { B f; int g; }; // { dg-error "use of deleted function" "" { target c++23 } }
>
> int
> main ()
> {
> E e;
> auto f = *e.bar ();
> - auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; };
> + auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; }; // { dg-error "use of deleted function" "" { target c++23 } }
> }
> diff --git a/gcc/testsuite/g++.dg/cpp23/decltype1.C b/gcc/testsuite/g++.dg/cpp23/decltype1.C
> new file mode 100644
> index 00000000000..6f3cd0d45d5
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/decltype1.C
> @@ -0,0 +1,113 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Tests from P2266R1, decltype-related changes in
> +// $ 3.2.1. Interaction with decltype and decltype(auto)
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +auto f1(int x) -> decltype(x) { return (x); }
> +static_assert(same_type<decltype(f1), int (int)>::value);
> +auto f2(int x) -> decltype((x)) { return (x); } // { dg-error "cannot bind" }
> +static_assert(same_type<decltype(f2), int& (int)>::value);
> +auto f3(int x) -> decltype(auto) { return (x); } // { dg-warning "reference to local variable" }
> +static_assert(same_type<decltype(f3), int&& (int)>::value);
> +auto g1(int x) -> decltype(x) { return x; }
> +static_assert(same_type<decltype(g1), int (int)>::value);
> +auto g2(int x) -> decltype((x)) { return x; } // { dg-error "cannot bind" }
> +static_assert(same_type<decltype(g2), int& (int)>::value);
> +auto g3(int x) -> decltype(auto) { return x; }
> +static_assert(same_type<decltype(g3), int (int)>::value);
> +
> +// Note that f2 and g2 are well-formed in C++20, but we propose to make
> +// f2 and g2 ill-formed, because they attempt to bind an lvalue reference
> +// to a move-eligible xvalue expression.
> +
> +struct X { };
> +
> +auto
> +f4 (X x)
> +{
> + return x;
> +}
> +static_assert(same_type<decltype(f4), X(X)>::value);
> +
> +auto&
> +f5 (X x)
> +{
> + return x; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +static_assert(same_type<decltype(f5), X&(X)>::value);
> +
> +auto&&
> +f6 (X x)
> +{
> + return x; // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f6), X&&(X)>::value);
> +
> +auto
> +f7 (X x)
> +{
> + return (x);
> +}
> +static_assert(same_type<decltype(f7), X(X)>::value);
> +
> +auto&
> +f8 (X x)
> +{
> + return (x); // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +static_assert(same_type<decltype(f8), X&(X)>::value);
> +
> +auto&&
> +f9 (X x)
> +{
> + return (x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f9), X&&(X)>::value);
> +
> +decltype(auto)
> +f10 (X x)
> +{
> + return x;
> +}
> +static_assert(same_type<decltype(f10), X(X)>::value);
> +
> +decltype(auto)
> +f11 (X x)
> +{
> + return (x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f11), X&&(X)>::value);
> +
> +decltype(auto)
> +f12 (X& x)
> +{
> + return x;
> +}
> +static_assert(same_type<decltype(f12), X&(X&)>::value);
> +
> +decltype(auto)
> +f13 (X& x)
> +{
> + return (x);
> +}
> +static_assert(same_type<decltype(f13), X&(X&)>::value);
> +
> +decltype(auto)
> +f14 (X&& x)
> +{
> + return x;
> +}
> +static_assert(same_type<decltype(f14), X&&(X&&)>::value);
> +
> +decltype(auto)
> +f15 (X&& x)
> +{
> + return (x);
> +}
> +static_assert(same_type<decltype(f15), X&&(X&&)>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/decltype2.C b/gcc/testsuite/g++.dg/cpp23/decltype2.C
> new file mode 100644
> index 00000000000..84679c48f82
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/decltype2.C
> @@ -0,0 +1,49 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test decltype(auto) more.
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +struct Widget {
> + int x;
> +};
> +
> +Widget wg;
> +
> +decltype(auto) fn0(Widget&& x) {
> + return (::wg);
> +}
> +static_assert(same_type<decltype(fn0), Widget& (Widget&&)>::value);
> +
> +decltype(auto) fn1(Widget&& x) {
> + return ::wg;
> +}
> +static_assert(same_type<decltype(fn1), Widget (Widget&&)>::value);
> +
> +decltype(auto) fn2() {
> + Widget w;
> + return w;
> +}
> +static_assert(same_type<decltype(fn2), Widget ()>::value);
> +
> +decltype(auto) fn3() {
> + Widget w;
> + return (w); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(fn3), Widget&& ()>::value);
> +
> +decltype(auto) fn4() {
> + Widget w;
> + return w.x;
> +}
> +static_assert(same_type<decltype(fn4), int ()>::value);
> +
> +decltype(auto) fn5() {
> + Widget w;
> + return (w.x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(fn5), int& ()>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision1.C b/gcc/testsuite/g++.dg/cpp23/elision1.C
> new file mode 100644
> index 00000000000..8115739b7f9
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision1.C
> @@ -0,0 +1,109 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Tests from P2266R1.
> +
> +namespace std {
> + template<typename _Tp>
> + struct remove_reference
> + { typedef _Tp type; };
> +
> + template<typename _Tp>
> + struct remove_reference<_Tp&>
> + { typedef _Tp type; };
> +
> + template<typename _Tp>
> + struct remove_reference<_Tp&&>
> + { typedef _Tp type; };
> +
> + template<typename _Tp>
> + constexpr typename std::remove_reference<_Tp>::type&&
> + move(_Tp&& __t) noexcept
> + { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +struct Widget {
> + Widget(Widget&&);
> +};
> +
> +struct RRefTaker {
> + RRefTaker(Widget&&);
> +};
> +
> +struct Mutt {
> + operator int*() &&;
> +};
> +
> +struct Jeff {
> + operator int&() &&;
> +};
> +
> +Widget one(Widget w) {
> + return w; // OK since C++11
> +}
> +
> +RRefTaker two(Widget w) {
> + return w; // OK since C++11 + CWG1579
> +}
> +
> +RRefTaker three(Widget&& w) {
> + return w; // OK since C++20 because P0527
> +}
> +
> +// Tests that implicit move applies even to functions that return references.
> +Widget&& four(Widget&& w) {
> + return w; // OK since C++23
> +}
> +
> +// FIXME This is supposed to work but we reject it with
> +// error: passing 'Mutt' as 'this' argument discards qualifiers
> +#if 0
> +int* five(Mutt x) {
> + return x; // OK since C++20 because P1155
> +}
> +#endif
> +
> +int& six(Jeff x) {
> + return x;
> +}
> +
> +template<class T>
> +T&& seven(T&& x) { return x; }
> +
> +void test_seven(Widget w) {
> + Widget& r = seven(w);
> + Widget&& rr = seven(std::move(w));
> +}
> +
> +Widget val();
> +Widget& lref();
> +Widget&& rref();
> +
> +decltype(auto) eight() {
> + decltype(auto) x = val(); // OK, x is Widget
> + return x; // OK, return type is Widget, we get copy elision
> +}
> +
> +decltype(auto) nine() {
> + decltype(auto) x = lref(); // OK, x is Widget&
> + return x; // OK, return type is Widget&
> +}
> +
> +decltype(auto) ten() {
> + decltype(auto) x = rref(); // OK, x is Widget&&
> + // This was an error: return type is Widget&&, cannot bind to x.
> + // But in C++23, x is treated as an rvalue.
> + return x;
> +}
> +
> +// Now returns Widget&&, not Widget&.
> +// This is from $ 3.2.1. Interaction with decltype and decltype(auto).
> +decltype(auto) eleven(Widget&& x) {
> + return (x);
> +}
> +static_assert(same_type<decltype(eleven), Widget&& (Widget&&)>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision2.C b/gcc/testsuite/g++.dg/cpp23/elision2.C
> new file mode 100644
> index 00000000000..ce2c7aeef66
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision2.C
> @@ -0,0 +1,46 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++20 } }
> +// Test from P2266R1, $ 3.3. Two overload resolutions are overly confusing.
> +
> +struct Widget {
> + Widget();
> + Widget(Widget&&);
> +};
> +
> +struct Frodo {
> + Frodo(Widget&);
> + Frodo(Widget&&) = delete;
> +};
> +
> +struct Sam {
> + Sam(Widget&) = delete; // #1
> + Sam(const Widget&); // #2
> +};
> +
> +Sam twelve() {
> + Widget w;
> + // This is supposed to calls #2 since C++20 because P1155.
> + // But we actually choose #1 since r11-2411 (in C++20 only).
> + return w; // { dg-error "deleted" "" { target c++20_only } }
> +}
> +
> +Frodo thirteen() {
> + Widget w;
> + // This is a correct error in both C++20 and C++23.
> + return w; // { dg-error "use of deleted function" }
> +}
> +
> +struct Merry {};
> +struct Pippin {};
> +struct Together : Merry, Pippin {};
> +struct Quest {
> + Quest(Merry&&);
> + Quest(Pippin&&);
> + Quest(Together&);
> +};
> +
> +Quest fourteen() {
> + Together t;
> + // C++20: calls Quest(Together&). Proposed: ill-formed.
> + return t; // { dg-error "ambiguous" "" { target c++23 } }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision3.C b/gcc/testsuite/g++.dg/cpp23/elision3.C
> new file mode 100644
> index 00000000000..246342e64d3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision3.C
> @@ -0,0 +1,16 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from P2266R1, $ 3.4. A specific case involving reference_wrapper.
> +
> +#include <functional>
> +
> +struct Widget {
> + Widget();
> + Widget(Widget&&);
> +};
> +
> +std::reference_wrapper<Widget> fifteen() {
> + Widget w;
> + // OK until CWG1579; OK after LWG2993. Proposed: ill-formed
> + return w; // { dg-error "could not convert" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
> new file mode 100644
> index 00000000000..c19b86b8b5f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
> @@ -0,0 +1,38 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from P2266R1, $ 5.2. LibreOffice OString constructor.
> +
> +struct X {
> + X(auto&);
> +};
> +
> +// The following compiles in C++20 (deducing X(char (&)[10])) but not
> +// after P2266 (because the returned expression now has type char (&&)[10],
> +// which cannot bind to auto&).
> +X f() {
> + char a[10];
> + return a; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +// The solution was to change it by making the return convert explicitly
> +// rather than implicitly:
> +X fixed() {
> + char a[10];
> + return X(a);
> +}
> +
> +// $ 5.3. LibreOffice o3tl::temporary
> +
> +template<class T>
> +T& temporary1(T&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
> +
> +// Fixed by:
> +template<class T>
> +T& temporary2(T&& x) { return static_cast<T&>(x); }
> +
> +void
> +test ()
> +{
> + int& r1 = temporary1 (42);
> + int& r2 = temporary2 (42);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision5.C b/gcc/testsuite/g++.dg/cpp23/elision5.C
> new file mode 100644
> index 00000000000..a7d3e7c27c4
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision5.C
> @@ -0,0 +1,53 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from [class.copy.elision]/4.
> +
> +class Thing {
> +public:
> + Thing();
> + ~Thing();
> + Thing(Thing&&);
> +private:
> + Thing(const Thing&);
> +};
> +
> +Thing f(bool b) {
> + Thing t;
> + if (b)
> + throw t; // OK, Thing(Thing&&) used (or elided) to throw t
> + return t; // OK, Thing(Thing&&) used (or elided) to return t
> +}
> +
> +Thing t2 = f(false); // OK, no extra copy/move performed, t2 constructed by call to f
> +
> +struct Weird {
> + Weird();
> + Weird(Weird&);
> +};
> +
> +Weird g(bool b) {
> + static Weird w1;
> + Weird w2;
> + if (b) {
> + return w1; // OK: Weird(Weird&)
> + } else {
> + return w2; // { dg-error "cannot bind non-const lvalue reference" }
> + }
> +}
> +
> +int& h(bool b, int i) {
> + static int s;
> + if (b)
> + return s; // OK
> + else
> + return i; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +decltype(auto) h2(Thing t) {
> + return t; // OK, t is an xvalue and h2's return type is Thing
> +}
> +
> +decltype(auto) h3(Thing t) {
> + // OK, (t) is an xvalue and h3's return type is Thing&&
> + return (t); // { dg-warning "reference to local variable" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision6.C b/gcc/testsuite/g++.dg/cpp23/elision6.C
> new file mode 100644
> index 00000000000..5d58da9e577
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision6.C
> @@ -0,0 +1,20 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// From [diff.cpp20.expr].
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +// In C++23, returns int&&; previously returned int&.
> +decltype(auto) f(int&& x) { return (x); }
> +static_assert(same_type<decltype(f), int&& (int&&)>::value);
> +
> +// This used to work in C++20.
> +int& g(int&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
> +
> +template<typename T>
> +decltype(auto) h(T&& x) { return (x); }
> +static_assert(same_type<decltype(h(42)), int&&>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
> new file mode 100644
> index 00000000000..19fa89ae133
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
> @@ -0,0 +1,72 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +
> +struct X {
> + X ();
> + X(X&&);
> +};
> +
> +X&& rref ();
> +
> +X&&
> +f1 (X&& x)
> +{
> + return x;
> +}
> +
> +template<typename T> T&&
> +f2 (T&& x)
> +{
> + return x;
> +}
> +template X& f2<X&>(X&);
> +template X&& f2<X>(X&&);
> +
> +X&&
> +f3 ()
> +{
> + X&& x = rref ();
> + return x;
> +}
> +
> +void
> +f4 ()
> +try {
> + X x;
> + throw x;
> +} catch (...) { }
> +
> +void
> +f5 ()
> +{
> + auto l1 = [](auto x) -> auto { return x; };
> + auto &&x1 = l1(X{});
> + auto l2 = [](auto x) -> auto& { return x; }; // { dg-error "cannot bind non-const lvalue reference" }
> + auto &&x2 = l2(X{});
> + auto l3 = [](auto x) -> auto&& { return x; }; // { dg-warning "reference to local" }
> + auto &&x3 = l3(X{});
> +}
> +
> +constexpr int &
> +f6 (int &&n)
> +{
> + return n; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +void
> +do_f6 ()
> +{
> + auto x = f6 (42);
> +}
> +
> +template<typename T> auto &
> +f7 (T &&t)
> +{
> + return t; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +void
> +do_f7 ()
> +{
> + const int &x = f7 (0);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> index d3e40724085..20453bb7b14 100644
> --- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> +++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> @@ -563,3 +563,9 @@
> #elif __cpp_named_character_escapes != 202207
> # error "__cpp_named_character_escapes != 202207"
> #endif
> +
> +#ifndef __cpp_implicit_move
> +# error "__cpp_implicit_move"
> +#elif __cpp_implicit_move != 202207
> +# error "__cpp_implicit_move != 202207"
> +#endif
> diff --git a/gcc/testsuite/g++.dg/gomp/pr56217.C b/gcc/testsuite/g++.dg/gomp/pr56217.C
> index 03dfc5f180b..731c0c08811 100644
> --- a/gcc/testsuite/g++.dg/gomp/pr56217.C
> +++ b/gcc/testsuite/g++.dg/gomp/pr56217.C
> @@ -1,5 +1,5 @@
> // PR middle-end/56217
> -// { dg-do compile }
> +// { dg-do compile { target c++20_down } }
> // { dg-options "-fopenmp" }
>
> struct S { int *p; S (); S (S &); };
> @@ -10,5 +10,7 @@ foo ()
> S s;
> #pragma omp task shared (s)
> s.p = 0;
> + // This fails in C++23, because "cannot bind non-const lvalue reference of
> + // type 'S&' to an rvalue of type 'S'".
> return s;
> }
> diff --git a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> index e15bfa24f54..cc9bb59770e 100644
> --- a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> +++ b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> @@ -4,7 +4,7 @@
> int& bad1()
> {
> int x = 0;
> - return x;
> + return x; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
> }
>
> int* bad2()
> diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> index 642a5767e84..4c18c2f06a0 100644
> --- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> +++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> @@ -5,7 +5,7 @@
> int& bad1()
> {
> int x = 0;
> - return x; // { dg-error "reference to local variable" }
> + return x; // { dg-error "reference to local variable|cannot bind non-const lvalue reference" }
> }
>
> int* bad2()
> diff --git a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> index fd4d4b65edb..b93e6e0c695 100644
> --- a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> +++ b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> @@ -8,5 +8,6 @@
>
> local = x+2;
>
> - return local; // { dg-warning "reference to local" }
> + return local; // { dg-warning "reference to local" "" { target c++20_down } }
> +// { dg-error "non-const lvalue" "" { target c++23 } .-1 }
> }
> diff --git a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> index c855f8f4a07..2709b50e7f1 100644
> --- a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> +++ b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> @@ -8,7 +8,7 @@ public:
> int i;
> };
>
> -X foo() { X x; return x; }
> +X foo() { X x; return x; } // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>
> int main()
> {
> diff --git a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> index 57422fe64df..68826649cfc 100644
> --- a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> +++ b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> @@ -1,4 +1,4 @@
> -// { dg-do run }
> +// { dg-do run { target c++20_down } }
> // Shows that problem of initializing one object's secondary base from
> // another object via a user defined copy constructor for that base,
> // the pointer for the secondary vtable is not set after implicit
> @@ -11,6 +11,8 @@
>
> // prms-id: 2846
>
> +// This test fails in C++23 due to P2266.
> +
> extern "C" int printf(const char *, ...);
> extern "C" void exit(int);
>
>
> base-commit: 10d6109fe183d984a0377a7afe2854a0d794ebeb
next prev parent reply other threads:[~2022-09-26 17:29 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-09-03 16:42 [PATCH] " Marek Polacek
2022-09-07 2:38 ` Jason Merrill
2022-09-08 22:54 ` Marek Polacek
2022-09-12 20:27 ` Jason Merrill
2022-09-20 18:21 ` Marek Polacek
2022-09-20 18:19 ` [PATCH v2] " Marek Polacek
2022-09-26 17:29 ` Jason Merrill [this message]
2022-09-27 20:26 ` [PATCH v3] " Marek Polacek
2022-09-27 21:44 ` Jason Merrill
2022-09-27 23:39 ` Marek Polacek
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=548cdcb9-abed-ef66-0dfd-5264cfa4ff3c@redhat.com \
--to=jason@redhat.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=polacek@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).