public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
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


  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).