From: Jason Merrill <jason@redhat.com>
To: Marek Polacek <polacek@redhat.com>,
GCC Patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATCH] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
Date: Tue, 6 Sep 2022 22:38:12 -0400 [thread overview]
Message-ID: <9f9f6d62-c3c8-083e-d30d-808076c01eca@redhat.com> (raw)
In-Reply-To: <20220903164200.17908-1-polacek@redhat.com>
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.
> 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.
> 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.
> - && !(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...
> + /* 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?
> + else
> + r = retval;
> + type = do_auto_deduction (pattern, r, auto_node,
> tf_warning_or_error, adc_return_type);
> }
>
> 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: 08de065293f8b08158e1089fbacce9dbaba95077
next prev parent reply other threads:[~2022-09-07 2:38 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-09-03 16:42 Marek Polacek
2022-09-07 2:38 ` Jason Merrill [this message]
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
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=9f9f6d62-c3c8-083e-d30d-808076c01eca@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).