public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477]
@ 2022-07-08 17:41 Marek Polacek
  2022-07-11 10:19 ` Jonathan Wakely
  2022-07-12 20:10 ` [PATCH] " Jason Merrill
  0 siblings, 2 replies; 15+ messages in thread
From: Marek Polacek @ 2022-07-08 17:41 UTC (permalink / raw)
  To: GCC Patches, libstdc++, Jason Merrill, Jonathan Wakely

This patch implements C++23 P2255R2, which adds two new type traits to
detect reference binding to a temporary.  They can be used to detect code
like

  std::tuple<const std::string&> t("meow");

which is incorrect because it always creates a dangling reference, because
the std::string temporary is created inside the selected constructor of
std::tuple, and not outside it.

There are two new compiler builtins, __reference_constructs_from_temporary
and __reference_converts_from_temporary.  The former is used to simulate
direct- and the latter copy-initialization context.  But I had a hard time
finding a test where there's actually a difference.  Under DR 2267, both
of these are invalid:

  struct A { } a;
  struct B { explicit B(const A&); };
  const B &b1{a};
  const B &b2(a);

so I had to peruse [over.match.ref], and eventually realized that the
difference can be seen here:

  struct G {
    operator int(); // #1
    explicit operator int&&(); // #2
  };

int&& r1(G{}); // use #2 (no temporary)
int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)

The implementation itself was rather straightforward because we already
have conv_binds_ref_to_prvalue.  The main function here is
reference_from_temporary.  The renaming to ref_conv_binds_to_temporary_p
is because previously the function didn't distinguish between an invalid
conversion and one that binds to a prvalue.

The patch also adds the relevant class and variable templates to <type_traits>.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

	PR c++/104477

gcc/c-family/ChangeLog:

	* c-common.cc (c_common_reswords): Add
	__reference_constructs_from_temporary and
	__reference_converts_from_temporary.
	* c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
	RID_REF_CONVERTS_FROM_TEMPORARY.

gcc/cp/ChangeLog:

	* call.cc (ref_conv_binds_directly_p): Rename to ...
	(ref_conv_binds_to_temporary_p): ... this.  Add a new bool
	parameter.  Return true only if the conversion is valid and
	conv_binds_ref_to_prvalue returns true.
	* constraint.cc (diagnose_trait_expr): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	* cp-tree.h (enum cp_trait_kind): Add CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
	and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	(ref_conv_binds_directly_p): Rename to ...
	(ref_conv_binds_to_temporary_p): ... this.
	(reference_from_temporary): Declare.
	* cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	* method.cc (reference_from_temporary): New.
	* parser.cc (cp_parser_primary_expression): Handle
	RID_REF_CONSTRUCTS_FROM_TEMPORARY and RID_REF_CONVERTS_FROM_TEMPORARY.
	(cp_parser_trait_expr): Likewise.
	(warn_for_range_copy): Adjust to call ref_conv_binds_to_temporary_p.
	* semantics.cc (trait_expr_value): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	(finish_trait_expr): Likewise.

libstdc++-v3/ChangeLog:

	* include/std/type_traits (reference_constructs_from_temporary,
	reference_converts_from_temporary): New class templates.
	(reference_constructs_from_temporary_v,
	reference_converts_from_temporary_v): New variable templates.
	(__cpp_lib_reference_from_temporary): Define for C++23.
	* include/std/version (__cpp_lib_reference_from_temporary): Define for
	C++23.
	* testsuite/20_util/variable_templates_for_traits.cc: Test
	reference_constructs_from_temporary_v and
	reference_converts_from_temporary_v.
	* testsuite/20_util/reference_from_temporary/value.cc: New test.
	* testsuite/20_util/reference_from_temporary/value2.cc: New test.
	* testsuite/20_util/reference_from_temporary/version.cc: New test.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/reference_constructs_from_temporary1.C: New test.
	* g++.dg/ext/reference_converts_from_temporary1.C: New test.
---
 gcc/c-family/c-common.cc                      |   4 +
 gcc/c-family/c-common.h                       |   2 +
 gcc/cp/call.cc                                |  14 +-
 gcc/cp/constraint.cc                          |   8 +
 gcc/cp/cp-tree.h                              |   7 +-
 gcc/cp/cxx-pretty-print.cc                    |   6 +
 gcc/cp/method.cc                              |  28 +++
 gcc/cp/parser.cc                              |  14 +-
 gcc/cp/semantics.cc                           |   8 +
 .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
 .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
 libstdc++-v3/include/std/type_traits          |  39 ++++
 libstdc++-v3/include/std/version              |   5 +-
 .../20_util/reference_from_temporary/value.cc | 110 +++++++++
 .../reference_from_temporary/value2.cc        |  28 +++
 .../reference_from_temporary/version.cc       |  27 +++
 .../20_util/variable_templates_for_traits.cc  |  14 ++
 17 files changed, 730 insertions(+), 12 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 1b8e73f7bc5..655c3aefee6 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
   { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
   { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
   { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY },
+  { "__reference_constructs_from_temporary", RID_REF_CONSTRUCTS_FROM_TEMPORARY,
+					D_CXXONLY },
+  { "__reference_converts_from_temporary", RID_REF_CONVERTS_FROM_TEMPORARY,
+					D_CXXONLY },
 
   /* C++ transactional memory.  */
   { "synchronized",	RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c0900848965..f9064393b4e 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -184,6 +184,8 @@ enum rid
   RID_IS_UNION,                RID_UNDERLYING_TYPE,
   RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
   RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
+  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
+  RID_REF_CONVERTS_FROM_TEMPORARY,
 
   /* C++11 */
   RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index fc98552fda2..1ba209f61f1 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
   return conv_is_prvalue (next_conversion (c));
 }
 
-/* True iff converting EXPR to a reference type TYPE does not involve
-   creating a temporary.  */
+/* True iff converting EXPR to a reference type TYPE binds the reference to
+   a temporary.  DIRECT_INIT_P says whether the conversion should be done
+   in direct- or copy-initialization context.  */
 
 bool
-ref_conv_binds_directly_p (tree type, tree expr)
+ref_conv_binds_to_temporary_p (tree type, tree expr,
+			       bool direct_init_p /*= false*/)
 {
   gcc_assert (TYPE_REF_P (type));
 
   /* Get the high-water mark for the CONVERSION_OBSTACK.  */
   void *p = conversion_obstack_alloc (0);
 
+  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
   conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
-					  /*c_cast_p=*/false,
-					  LOOKUP_IMPLICIT, tf_none);
-  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
+					  /*c_cast_p=*/false, flags, tf_none);
+  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue (conv);
 
   /* Free all the conversions we allocated.  */
   obstack_free (&conversion_obstack, p);
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 591155cee22..648cc9d176d 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3687,6 +3687,14 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
       inform (loc, "  %qT does not have unique object representations", t1);
       break;
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      inform (loc, "  %qT is not a reference that binds to a temporary "
+	      "object of type %qT (direct-initialization)", t1, t2);
+      break;
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      inform (loc, "  %qT is not a reference that binds to a temporary "
+	      "object of type %qT (copy-initialization)", t1, t2);
+      break;
     case CPTK_BASES:
     case CPTK_DIRECT_BASES:
     case CPTK_UNDERLYING_TYPE:
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 2fde4f83b41..c3bed31e455 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1397,7 +1397,9 @@ enum cp_trait_kind
   CPTK_IS_ASSIGNABLE,
   CPTK_IS_CONSTRUCTIBLE,
   CPTK_IS_NOTHROW_ASSIGNABLE,
-  CPTK_IS_NOTHROW_CONSTRUCTIBLE
+  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
+  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
+  CPTK_REF_CONVERTS_FROM_TEMPORARY
 };
 
 /* The types that we are processing.  */
@@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p			(const_tree);
 extern tree type_decays_to			(tree);
 extern tree extract_call_expr			(tree);
 extern tree build_trivial_dtor_call		(tree, bool = false);
-extern bool ref_conv_binds_directly_p		(tree, tree);
+extern bool ref_conv_binds_to_temporary_p	(tree, tree, bool = false);
 extern tree build_user_type_conversion		(tree, tree, int,
 						 tsubst_flags_t);
 extern tree build_new_function_call		(tree, vec<tree, va_gc> **,
@@ -7105,6 +7107,7 @@ extern tree forward_parm			(tree);
 extern bool is_trivially_xible			(enum tree_code, tree, tree);
 extern bool is_nothrow_xible			(enum tree_code, tree, tree);
 extern bool is_xible				(enum tree_code, tree, tree);
+extern bool reference_from_temporary		(tree, tree, bool);
 extern tree get_defaulted_eh_spec		(tree, tsubst_flags_t = tf_warning_or_error);
 extern bool maybe_explain_implicit_delete	(tree);
 extern void explain_implicit_non_constexpr	(tree);
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 7e4db2e413b..44590830a61 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t)
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
       pp_cxx_ws_string (pp, "__is_nothrow_constructible");
       break;
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
+      break;
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
+      break;
 
     default:
       gcc_unreachable ();
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 0dffd648b0b..dd9715b6725 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to, tree from)
   return !!expr;
 }
 
+/* Return true iff conjunction_v<is_reference<T>, is_constructible<T, U>> is
+   true, and the initialization
+     T t(VAL<U>); // DIRECT_INIT_P
+   or
+     T t = VAL<U>; // !DIRECT_INIT_P
+   binds t to a temporary object whose lifetime is extended.
+   VAL<T> is defined in [meta.unary.prop]:
+   -- If T is a reference or function type, VAL<T> is an expression with the
+   same type and value category as declval<T>().
+   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
+
+bool
+reference_from_temporary (tree to, tree from, bool direct_init_p)
+{
+  /* Check is_reference<T>.  */
+  if (!TYPE_REF_P (to))
+    return false;
+  /* Check is_constructible<T, U>.
+     ??? This check doesn't seem to be necessary; if T isn't constructible
+     from U, we won't be able to create a conversion.  */
+  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
+    return false;
+  tree val = build_stub_object (from);
+  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
+    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) : rvalue (val);
+  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
+}
+
 /* Categorize various special_function_kinds.  */
 #define SFK_CTOR_P(sfk) \
   ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index bf9ea3685f8..edee94bda13 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
 	case RID_IS_CONSTRUCTIBLE:
 	case RID_IS_NOTHROW_ASSIGNABLE:
 	case RID_IS_NOTHROW_CONSTRUCTIBLE:
+	case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
+	case RID_REF_CONVERTS_FROM_TEMPORARY:
 	  return cp_parser_trait_expr (parser, token->keyword);
 
 	// C++ concepts
@@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
       kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
       variadic = true;
       break;
+    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
+      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
+      binary = true;
+      break;
+    case RID_REF_CONVERTS_FROM_TEMPORARY:
+      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
+      binary = true;
+      break;
     default:
       gcc_unreachable ();
     }
@@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
 
   if (TYPE_REF_P (type))
     {
-      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
+      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type, expr))
 	{
 	  auto_diagnostic_group d;
 	  if (warning_at (loc, OPT_Wrange_loop_construct,
@@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
   tree rtype = cp_build_reference_type (type, /*rval*/false);
   /* If we could initialize the reference directly, it wouldn't involve any
      copies.  */
-  if (!ref_conv_binds_directly_p (rtype, expr))
+  if (ref_conv_binds_to_temporary_p (rtype, expr))
     return;
 
   auto_diagnostic_group d;
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 2344b5eea00..396a9f6b44e 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -12007,6 +12007,12 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
       return is_nothrow_xible (INIT_EXPR, type1, type2);
 
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      return reference_from_temporary (type1, type2, /*direct_init=*/true);
+
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      return reference_from_temporary (type1, type2, /*direct_init=*/false);
+
     default:
       gcc_unreachable ();
       return false;
@@ -12088,6 +12094,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
     case CPTK_IS_NOTHROW_ASSIGNABLE:
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
       if (!check_trait_type (type1)
 	  || !check_trait_type (type2))
 	return error_mark_node;
diff --git a/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
new file mode 100644
index 00000000000..76de905a35d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
@@ -0,0 +1,214 @@
+// P2255R2
+// PR c++/104477
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+struct A { A(); };
+struct B { operator A(); };
+struct C { explicit C(); };
+struct D { explicit operator A(); };
+struct E { explicit operator A&(); };
+struct F { explicit operator A&&(); };
+// Could use a class template with explicit(bool), but then this would need
+// C++20.
+struct G {
+  operator int();
+  explicit operator int&&();
+};
+struct G2 {
+  explicit operator int();
+  operator int&&();
+};
+struct H {
+  operator int();
+  explicit operator int&();
+};
+struct H2 {
+  explicit operator int();
+  operator int&();
+};
+
+struct Base { };
+struct Der : Base { };
+
+template<typename T, typename RT>
+struct morph {
+  mutable T val{};
+  operator RT() const { return static_cast<RT>(val); }
+};
+
+template<typename T> using id = T;
+
+// Built-in types.
+SA(!__reference_constructs_from_temporary(int, int));
+SA(!__reference_constructs_from_temporary(int&, void));
+SA(!__reference_constructs_from_temporary(int&, const void));
+SA(!__reference_constructs_from_temporary(int&, volatile void));
+SA(!__reference_constructs_from_temporary(int&, const volatile void));
+SA(!__reference_constructs_from_temporary(void, void));
+SA(!__reference_constructs_from_temporary(void, int));
+SA(!__reference_constructs_from_temporary(int&, int));
+SA(!__reference_constructs_from_temporary(int&, int&));
+SA(!__reference_constructs_from_temporary(int&, int&&));
+SA(!__reference_constructs_from_temporary(int&, long));
+// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
+SA(!__reference_constructs_from_temporary(int&, long&));
+SA(!__reference_constructs_from_temporary(int&, long&&));
+SA( __reference_constructs_from_temporary(const int&, int));
+SA(!__reference_constructs_from_temporary(const int&, int&));
+SA(!__reference_constructs_from_temporary(const int&, const int&));
+SA(!__reference_constructs_from_temporary(const int&, int&&));
+SA( __reference_constructs_from_temporary(const int&, long));
+SA( __reference_constructs_from_temporary(const int&, long&));
+SA( __reference_constructs_from_temporary(const int&, long&&));
+SA( __reference_constructs_from_temporary(int&&, int));
+SA(!__reference_constructs_from_temporary(int&&, int&));
+SA(!__reference_constructs_from_temporary(int&&, int&&));
+SA( __reference_constructs_from_temporary(int&&, long));
+SA( __reference_constructs_from_temporary(int&&, long&));
+SA( __reference_constructs_from_temporary(int&&, long&&));
+SA(!__reference_constructs_from_temporary(unsigned int&, double));
+SA(!__reference_constructs_from_temporary(volatile int&, int));
+SA(!__reference_constructs_from_temporary(const volatile int&, int));
+SA(!__reference_constructs_from_temporary(volatile int&, int&));
+SA(!__reference_constructs_from_temporary(const volatile int&, int&));
+SA(!__reference_constructs_from_temporary(volatile int&, int&&));
+SA(!__reference_constructs_from_temporary(const volatile int&, int&&));
+
+// Classes.
+SA(!__reference_constructs_from_temporary(A, A));
+// A& r(A{}); doesn't construct.
+SA(!__reference_constructs_from_temporary(A&, A));
+SA(!__reference_constructs_from_temporary(A&, A&));
+SA(!__reference_constructs_from_temporary(A&, A&&));
+// Here we get const struct A & r = (const struct A &) &D.2414;
+SA( __reference_constructs_from_temporary(const A&, A));
+SA(!__reference_constructs_from_temporary(const A&, A&));
+SA(!__reference_constructs_from_temporary(const A&, const A&));
+SA(!__reference_constructs_from_temporary(const A&, A&&));
+// Here we get struct A & r = (struct A &) &D.2439;
+SA( __reference_constructs_from_temporary(A&&, A));
+SA(!__reference_constructs_from_temporary(A&&, A&));
+SA(!__reference_constructs_from_temporary(A&&, const A&));
+SA(!__reference_constructs_from_temporary(A&&, A&&));
+
+SA(!__reference_constructs_from_temporary(A, B));
+SA(!__reference_constructs_from_temporary(A&, B));
+SA(!__reference_constructs_from_temporary(A&, B&));
+SA(!__reference_constructs_from_temporary(A&, const B&));
+SA(!__reference_constructs_from_temporary(A&, B&&));
+SA( __reference_constructs_from_temporary(const A&, B));
+SA( __reference_constructs_from_temporary(const A&, B&));
+// Doesn't construct, so it's false.
+SA(!__reference_constructs_from_temporary(const A&, const B&));
+SA( __reference_constructs_from_temporary(const A&, B&&));
+SA( __reference_constructs_from_temporary(A&&, B));
+SA( __reference_constructs_from_temporary(A&&, B&));
+SA(!__reference_constructs_from_temporary(A&&, const B&));
+SA( __reference_constructs_from_temporary(A&&, B&&));
+
+SA(!__reference_constructs_from_temporary(const A&, C));
+SA(!__reference_constructs_from_temporary(const A&, C&));
+
+// Doesn't construct, so it's false.
+SA(!__reference_constructs_from_temporary(int&, morph<int, int>));
+// Here we get
+//   const int & r2 = D.2580 = morph<int, int>::operator int
+//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
+SA( __reference_constructs_from_temporary(const int&, morph<int, int>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, int&>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, const int&>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, int&&>));
+SA( __reference_constructs_from_temporary(const int&, morph<long, long&>));
+
+// These are like const C& c(cref); so the explicit ctor C isn't a problem
+// even in copy-init context.  const C& r = {}; would be a different story.
+SA(!__reference_constructs_from_temporary(C, C));
+SA(!__reference_constructs_from_temporary(C&, C));
+SA(!__reference_constructs_from_temporary(C&, C&));
+SA(!__reference_constructs_from_temporary(C&, C&&));
+SA( __reference_constructs_from_temporary(const C&, C));
+SA(!__reference_constructs_from_temporary(const C&, C&));
+SA(!__reference_constructs_from_temporary(const C&, const C&));
+SA(!__reference_constructs_from_temporary(const C&, C&&));
+SA( __reference_constructs_from_temporary(C&&, C));
+SA(!__reference_constructs_from_temporary(C&&, C&));
+SA(!__reference_constructs_from_temporary(C&&, const C&));
+SA(!__reference_constructs_from_temporary(C&&, C&&));
+
+// These are all false ultimately because of CWG 2267, which we implement.
+SA(!__reference_constructs_from_temporary(A, D));
+SA(!__reference_constructs_from_temporary(A&, D));
+SA(!__reference_constructs_from_temporary(A&, D&));
+SA(!__reference_constructs_from_temporary(A&, const D&));
+SA(!__reference_constructs_from_temporary(A&, D&&));
+SA(!__reference_constructs_from_temporary(const A&, D));
+SA(!__reference_constructs_from_temporary(const A&, D&));
+SA(!__reference_constructs_from_temporary(const A&, const D&));
+SA(!__reference_constructs_from_temporary(const A&, D&&));
+SA(!__reference_constructs_from_temporary(A&&, D));
+SA(!__reference_constructs_from_temporary(A&&, D&));
+SA(!__reference_constructs_from_temporary(A&&, const D&));
+SA(!__reference_constructs_from_temporary(A&&, D&&));
+
+SA(!__reference_constructs_from_temporary(A, E));
+/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
+   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
+   so we're not binding the reference to a temporary, although there is
+   a temporary involved.  So the result is false in both copy- and direct-
+   init, albeit for different reasons!  */
+SA(!__reference_constructs_from_temporary(A&, E));
+// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
+SA(!__reference_constructs_from_temporary(A&, E&));
+SA(!__reference_constructs_from_temporary(A&, const E&));
+SA(!__reference_constructs_from_temporary(A&, E&&));
+// direct-init:
+// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
+SA(!__reference_constructs_from_temporary(const A&, E));
+SA(!__reference_constructs_from_temporary(const A&, E&));
+SA(!__reference_constructs_from_temporary(const A&, const E&));
+SA(!__reference_constructs_from_temporary(const A&, E&&));
+SA(!__reference_constructs_from_temporary(A&&, E));
+SA(!__reference_constructs_from_temporary(A&&, E&));
+SA(!__reference_constructs_from_temporary(A&&, const E&));
+SA(!__reference_constructs_from_temporary(A&&, E&&));
+
+SA(!__reference_constructs_from_temporary(A, F));
+// A& a1(F{}); and A& a2 = F{}; both invalid.
+SA(!__reference_constructs_from_temporary(A&, F));
+SA(!__reference_constructs_from_temporary(A&, F&));
+SA(!__reference_constructs_from_temporary(A&, const F&));
+SA(!__reference_constructs_from_temporary(A&, F&&));
+SA(!__reference_constructs_from_temporary(const A&, F));
+SA(!__reference_constructs_from_temporary(const A&, F&));
+SA(!__reference_constructs_from_temporary(const A&, const F&));
+SA(!__reference_constructs_from_temporary(const A&, F&&));
+SA(!__reference_constructs_from_temporary(A&&, F));
+SA(!__reference_constructs_from_temporary(A&&, F&));
+SA(!__reference_constructs_from_temporary(A&&, const F&));
+SA(!__reference_constructs_from_temporary(A&&, F&&));
+
+/* This is where _converts_ and _constructs_ will differ:
+   in direct-init we use G::operator int&& (no temporary),
+   but in copy-init we use G::operator int, where a temporary is created
+   to be bound to int&&.  */
+SA(!__reference_constructs_from_temporary(int&&, G));
+// Similar to the previous one.
+SA(!__reference_constructs_from_temporary(const int&, H));
+/* And here I've switched the explicit-ness.  In both copy- and direct-init
+   we call operator int&, so no temporary.  */
+SA(!__reference_constructs_from_temporary(int&&, G2));
+SA(!__reference_constructs_from_temporary(const int&, H2));
+
+SA(!__reference_constructs_from_temporary(const Base&, Der));
+
+// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
+SA(!__reference_constructs_from_temporary(int&&, id<int[3]>));
+
+// Arrays.
+SA(!__reference_constructs_from_temporary(int, int[]));
+SA(!__reference_constructs_from_temporary(int[], int[]));
+SA(!__reference_constructs_from_temporary(int&, int[]));
+SA(!__reference_constructs_from_temporary(int&&, int[]));
+SA(!__reference_constructs_from_temporary(const int&, int[]));
diff --git a/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
new file mode 100644
index 00000000000..90196c38742
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
@@ -0,0 +1,214 @@
+// P2255R2
+// PR c++/104477
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+struct A { A(); };
+struct B { operator A(); };
+struct C { explicit C(); };
+struct D { explicit operator A(); };
+struct E { explicit operator A&(); };
+struct F { explicit operator A&&(); };
+// Could use a class template with explicit(bool), but then this would need
+// C++20.
+struct G {
+  operator int();
+  explicit operator int&&();
+};
+struct G2 {
+  explicit operator int();
+  operator int&&();
+};
+struct H {
+  operator int();
+  explicit operator int&();
+};
+struct H2 {
+  explicit operator int();
+  operator int&();
+};
+
+struct Base { };
+struct Der : Base { };
+
+template<typename T, typename RT>
+struct morph {
+  mutable T val{};
+  operator RT() const { return static_cast<RT>(val); }
+};
+
+template<typename T> using id = T;
+
+// Built-in types.
+SA(!__reference_converts_from_temporary(int, int));
+SA(!__reference_converts_from_temporary(int&, void));
+SA(!__reference_converts_from_temporary(int&, const void));
+SA(!__reference_converts_from_temporary(int&, volatile void));
+SA(!__reference_converts_from_temporary(int&, const volatile void));
+SA(!__reference_converts_from_temporary(void, void));
+SA(!__reference_converts_from_temporary(void, int));
+SA(!__reference_converts_from_temporary(int&, int));
+SA(!__reference_converts_from_temporary(int&, int&));
+SA(!__reference_converts_from_temporary(int&, int&&));
+SA(!__reference_converts_from_temporary(int&, long));
+// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
+SA(!__reference_converts_from_temporary(int&, long&));
+SA(!__reference_converts_from_temporary(int&, long&&));
+SA( __reference_converts_from_temporary(const int&, int));
+SA(!__reference_converts_from_temporary(const int&, int&));
+SA(!__reference_converts_from_temporary(const int&, const int&));
+SA(!__reference_converts_from_temporary(const int&, int&&));
+SA( __reference_converts_from_temporary(const int&, long));
+SA( __reference_converts_from_temporary(const int&, long&));
+SA( __reference_converts_from_temporary(const int&, long&&));
+SA( __reference_converts_from_temporary(int&&, int));
+SA(!__reference_converts_from_temporary(int&&, int&));
+SA(!__reference_converts_from_temporary(int&&, int&&));
+SA( __reference_converts_from_temporary(int&&, long));
+SA( __reference_converts_from_temporary(int&&, long&));
+SA( __reference_converts_from_temporary(int&&, long&&));
+SA(!__reference_converts_from_temporary(unsigned int&, double));
+SA(!__reference_converts_from_temporary(volatile int&, int));
+SA(!__reference_converts_from_temporary(const volatile int&, int));
+SA(!__reference_converts_from_temporary(volatile int&, int&));
+SA(!__reference_converts_from_temporary(const volatile int&, int&));
+SA(!__reference_converts_from_temporary(volatile int&, int&&));
+SA(!__reference_converts_from_temporary(const volatile int&, int&&));
+
+// Classes.
+SA(!__reference_converts_from_temporary(A, A));
+// A& r(A{}); doesn't construct.
+SA(!__reference_converts_from_temporary(A&, A));
+SA(!__reference_converts_from_temporary(A&, A&));
+SA(!__reference_converts_from_temporary(A&, A&&));
+// Here we get const struct A & r = (const struct A &) &D.2414;
+SA( __reference_converts_from_temporary(const A&, A));
+SA(!__reference_converts_from_temporary(const A&, A&));
+SA(!__reference_converts_from_temporary(const A&, const A&));
+SA(!__reference_converts_from_temporary(const A&, A&&));
+// Here we get struct A & r = (struct A &) &D.2439;
+SA( __reference_converts_from_temporary(A&&, A));
+SA(!__reference_converts_from_temporary(A&&, A&));
+SA(!__reference_converts_from_temporary(A&&, const A&));
+SA(!__reference_converts_from_temporary(A&&, A&&));
+
+SA(!__reference_converts_from_temporary(A, B));
+SA(!__reference_converts_from_temporary(A&, B));
+SA(!__reference_converts_from_temporary(A&, B&));
+SA(!__reference_converts_from_temporary(A&, const B&));
+SA(!__reference_converts_from_temporary(A&, B&&));
+SA( __reference_converts_from_temporary(const A&, B));
+SA( __reference_converts_from_temporary(const A&, B&));
+// Doesn't construct, so it's false.
+SA(!__reference_converts_from_temporary(const A&, const B&));
+SA( __reference_converts_from_temporary(const A&, B&&));
+SA( __reference_converts_from_temporary(A&&, B));
+SA( __reference_converts_from_temporary(A&&, B&));
+SA(!__reference_converts_from_temporary(A&&, const B&));
+SA( __reference_converts_from_temporary(A&&, B&&));
+
+SA(!__reference_converts_from_temporary(const A&, C));
+SA(!__reference_converts_from_temporary(const A&, C&));
+
+// Doesn't construct, so it's false.
+SA(!__reference_converts_from_temporary(int&, morph<int, int>));
+// Here we get
+//   const int & r2 = D.2580 = morph<int, int>::operator int
+//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
+SA( __reference_converts_from_temporary(const int&, morph<int, int>));
+SA(!__reference_converts_from_temporary(int&, morph<int, int&>));
+SA(!__reference_converts_from_temporary(int&, morph<int, const int&>));
+SA(!__reference_converts_from_temporary(int&, morph<int, int&&>));
+SA( __reference_converts_from_temporary(const int&, morph<long, long&>));
+
+// These are like const C& c(cref); so the explicit ctor C isn't a problem
+// even in copy-init context.  const C& r = {}; would be a different story.
+SA(!__reference_converts_from_temporary(C, C));
+SA(!__reference_converts_from_temporary(C&, C));
+SA(!__reference_converts_from_temporary(C&, C&));
+SA(!__reference_converts_from_temporary(C&, C&&));
+SA( __reference_converts_from_temporary(const C&, C));
+SA(!__reference_converts_from_temporary(const C&, C&));
+SA(!__reference_converts_from_temporary(const C&, const C&));
+SA(!__reference_converts_from_temporary(const C&, C&&));
+SA( __reference_converts_from_temporary(C&&, C));
+SA(!__reference_converts_from_temporary(C&&, C&));
+SA(!__reference_converts_from_temporary(C&&, const C&));
+SA(!__reference_converts_from_temporary(C&&, C&&));
+
+// These are all false ultimately because of CWG 2267, which we implement.
+SA(!__reference_converts_from_temporary(A, D));
+SA(!__reference_converts_from_temporary(A&, D));
+SA(!__reference_converts_from_temporary(A&, D&));
+SA(!__reference_converts_from_temporary(A&, const D&));
+SA(!__reference_converts_from_temporary(A&, D&&));
+SA(!__reference_converts_from_temporary(const A&, D));
+SA(!__reference_converts_from_temporary(const A&, D&));
+SA(!__reference_converts_from_temporary(const A&, const D&));
+SA(!__reference_converts_from_temporary(const A&, D&&));
+SA(!__reference_converts_from_temporary(A&&, D));
+SA(!__reference_converts_from_temporary(A&&, D&));
+SA(!__reference_converts_from_temporary(A&&, const D&));
+SA(!__reference_converts_from_temporary(A&&, D&&));
+
+SA(!__reference_converts_from_temporary(A, E));
+/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
+   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
+   so we're not binding the reference to a temporary, although there is
+   a temporary involved.  So the result is false in both copy- and direct-
+   init, albeit for different reasons!  */
+SA(!__reference_converts_from_temporary(A&, E));
+// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
+SA(!__reference_converts_from_temporary(A&, E&));
+SA(!__reference_converts_from_temporary(A&, const E&));
+SA(!__reference_converts_from_temporary(A&, E&&));
+// direct-init:
+// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
+SA(!__reference_converts_from_temporary(const A&, E));
+SA(!__reference_converts_from_temporary(const A&, E&));
+SA(!__reference_converts_from_temporary(const A&, const E&));
+SA(!__reference_converts_from_temporary(const A&, E&&));
+SA(!__reference_converts_from_temporary(A&&, E));
+SA(!__reference_converts_from_temporary(A&&, E&));
+SA(!__reference_converts_from_temporary(A&&, const E&));
+SA(!__reference_converts_from_temporary(A&&, E&&));
+
+SA(!__reference_converts_from_temporary(A, F));
+// A& a1(F{}); and A& a2 = F{}; both invalid.
+SA(!__reference_converts_from_temporary(A&, F));
+SA(!__reference_converts_from_temporary(A&, F&));
+SA(!__reference_converts_from_temporary(A&, const F&));
+SA(!__reference_converts_from_temporary(A&, F&&));
+SA(!__reference_converts_from_temporary(const A&, F));
+SA(!__reference_converts_from_temporary(const A&, F&));
+SA(!__reference_converts_from_temporary(const A&, const F&));
+SA(!__reference_converts_from_temporary(const A&, F&&));
+SA(!__reference_converts_from_temporary(A&&, F));
+SA(!__reference_converts_from_temporary(A&&, F&));
+SA(!__reference_converts_from_temporary(A&&, const F&));
+SA(!__reference_converts_from_temporary(A&&, F&&));
+
+/* This is where _converts_ and _constructs_ will differ:
+   in direct-init we use G::operator int&& (no temporary),
+   but in copy-init we use G::operator int, where a temporary is created
+   to be bound to int&&.  */
+SA( __reference_converts_from_temporary(int&&, G));
+// Similar to the previous one.
+SA( __reference_converts_from_temporary(const int&, H));
+/* And here I've switched the explicit-ness.  In both copy- and direct-init
+   we call operator int&, so no temporary.  */
+SA(!__reference_converts_from_temporary(int&&, G2));
+SA(!__reference_converts_from_temporary(const int&, H2));
+
+SA(!__reference_converts_from_temporary(const Base&, Der));
+
+// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
+SA(!__reference_converts_from_temporary(int&&, id<int[3]>));
+
+// Arrays.
+SA(!__reference_converts_from_temporary(int, int[]));
+SA(!__reference_converts_from_temporary(int[], int[]));
+SA(!__reference_converts_from_temporary(int&, int[]));
+SA(!__reference_converts_from_temporary(int&&, int[]));
+SA(!__reference_converts_from_temporary(const int&, int[]));
diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index 2572d8edd69..bbeb049e1f9 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -3508,6 +3508,45 @@ template<typename _Ret, typename _Fn, typename... _Args>
   template<typename _Tp>
     inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
 
+#define __cpp_lib_reference_from_temporary 202202L
+
+  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
+  /// direct-initialization, and a temporary object would be bound to
+  /// the reference, false otherwise.
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    struct reference_constructs_from_temporary
+    : public __bool_constant<__reference_constructs_from_temporary(_Tp, _Up)>
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
+		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
+	"template argument must be a complete class or an unbounded array");
+    };
+
+  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
+  /// copy-initialization, and a temporary object would be bound to
+  /// the reference, false otherwise.
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    struct reference_converts_from_temporary
+    : public __bool_constant<__reference_converts_from_temporary(_Tp, _Up)>
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
+		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
+	"template argument must be a complete class or an unbounded array");
+    };
+
+  /// @ingroup variable_templates
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    inline constexpr bool reference_constructs_from_temporary_v
+      = reference_constructs_from_temporary<_Tp, _Up>::value;
+
+  /// @ingroup variable_templates
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    inline constexpr bool reference_converts_from_temporary_v
+      = reference_converts_from_temporary<_Tp, _Up>::value;
 #endif // C++23
 
 #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
index 22280e1a349..5edca2f3007 100644
--- a/libstdc++-v3/include/std/version
+++ b/libstdc++-v3/include/std/version
@@ -300,10 +300,11 @@
 #endif
 
 #if __cplusplus > 202002L
-// c++2b
+// c++23
 #define __cpp_lib_byteswap 202110L
 #define __cpp_lib_constexpr_typeinfo 202106L
 #define __cpp_lib_is_scoped_enum 202011L
+#define __cpp_lib_reference_from_temporary 202202L
 
 #if _GLIBCXX_HOSTED
 #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
@@ -335,7 +336,7 @@
 #define __cpp_lib_to_underlying 202102L
 #define __cpp_lib_unreachable 202202L
 #endif
-#endif // C++2b
+#endif // C++23
 #endif // C++20
 #endif // C++17
 #endif // C++14
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
new file mode 100644
index 00000000000..2f62e54d46d
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
@@ -0,0 +1,110 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <type_traits>
+#include <testsuite_tr1.h>
+
+#ifndef __cpp_lib_reference_from_temporary
+# error "Feature test macro for reference_from_temporary is missing in <version>"
+#elif __cpp_lib_reference_from_temporary < 202202L
+# error "Feature test macro for reference_from_temporary has wrong value in <version>"
+#endif
+
+void test01()
+{
+  using std::reference_constructs_from_temporary;
+  using std::reference_converts_from_temporary;
+  using namespace __gnu_test;
+
+  struct A { A(); };
+
+  struct B {
+    operator int();
+    explicit operator int&&();
+  };
+
+  struct C {
+    operator int();
+    explicit operator int&();
+  };
+
+  static_assert(test_property<reference_constructs_from_temporary, int, int>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, const volatile void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, void, void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long&&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long&&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const A&, A>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const A&, A&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, A&&, A>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int[]>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int[]>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int[]>(false), "");
+
+  static_assert(test_property<reference_converts_from_temporary, int, int>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, const volatile void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, void, void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long&&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long&&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const A&, A>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const A&, A&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, A&&, A>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int[]>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int[]>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int[]>(false), "");
+
+  static_assert(test_property<reference_constructs_from_temporary, int&&, B>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, C>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, B>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, C>(true), "");
+}
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
new file mode 100644
index 00000000000..65770754299
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <type_traits>
+#include <string>
+
+void test01()
+{
+  static_assert(std::reference_converts_from_temporary_v<const std::string&, const char*>);
+  static_assert(std::reference_constructs_from_temporary_v<const std::string&, const char*>);
+}
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
new file mode 100644
index 00000000000..f56e7c0dabc
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <version>
+
+#ifndef __cpp_lib_reference_from_temporary
+# error "Feature test macro for reference_from_temporary is missing in <version>"
+#elif __cpp_lib_reference_from_temporary < 202202L
+# error "Feature test macro for reference_from_temporary has wrong value in <version>"
+#endif
diff --git a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
index 9b3957f7d47..2b03ad7067d 100644
--- a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
+++ b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
@@ -346,3 +346,17 @@ static_assert(disjunction_v<false_type, false_type,
               true_type>, "");
 static_assert(!disjunction_v<false_type, false_type,
               false_type>, "");
+#if __cpp_lib_reference_from_temporary >= 202202L
+static_assert(std::reference_converts_from_temporary_v<int&&, int>
+	      && std::reference_converts_from_temporary_v<const int&, int>
+	      && !std::reference_converts_from_temporary_v<int&&, int&&>
+	      && !std::reference_converts_from_temporary_v<const int&, int&&>
+	      && std::reference_converts_from_temporary_v<int&&, long&&>
+	      && std::reference_converts_from_temporary_v<int&&, long>, "");
+static_assert(std::reference_constructs_from_temporary_v<int&&, int>
+	      && std::reference_constructs_from_temporary_v<const int&, int>
+	      && !std::reference_constructs_from_temporary_v<int&&, int&&>
+	      && !std::reference_constructs_from_temporary_v<const int&, int&&>
+	      && std::reference_constructs_from_temporary_v<int&&, long&&>
+	      && std::reference_constructs_from_temporary_v<int&&, long>, "");
+#endif

base-commit: 55bb77b50a5ec520f28978ac3fdac1983200e1f7
-- 
2.36.1


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477]
  2022-07-08 17:41 [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Marek Polacek
@ 2022-07-11 10:19 ` Jonathan Wakely
  2022-07-12 17:16   ` [PATCH v2] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
  2022-07-12 20:10 ` [PATCH] " Jason Merrill
  1 sibling, 1 reply; 15+ messages in thread
From: Jonathan Wakely @ 2022-07-11 10:19 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches, libstdc++, Jason Merrill

On Fri, 8 Jul 2022 at 18:41, Marek Polacek wrote:
> The patch also adds the relevant class and variable templates to <type_traits>.


+  template<typename _Tp, typename _Up>
+    struct reference_constructs_from_temp
orary
+    : public __bool_constant<__reference_constructs_from_temporary(_Tp, _Up)>

This can use bool_constant, as that was added in C++17.
__bool_constant is the internal equivalent for pre-C++17.

The library parts are fine with that change, thanks for implementing this!


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-11 10:19 ` Jonathan Wakely
@ 2022-07-12 17:16   ` Marek Polacek
  0 siblings, 0 replies; 15+ messages in thread
From: Marek Polacek @ 2022-07-12 17:16 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: GCC Patches, libstdc++, Jason Merrill

On Mon, Jul 11, 2022 at 11:19:19AM +0100, Jonathan Wakely wrote:
> On Fri, 8 Jul 2022 at 18:41, Marek Polacek wrote:
> > The patch also adds the relevant class and variable templates to <type_traits>.
> 
> 
> +  template<typename _Tp, typename _Up>
> +    struct reference_constructs_from_temp
> orary
> +    : public __bool_constant<__reference_constructs_from_temporary(_Tp, _Up)>
> 
> This can use bool_constant, as that was added in C++17.
> __bool_constant is the internal equivalent for pre-C++17.

Ah, I see, I didn't know std::bool_constant was a C++17 thing!
 
> The library parts are fine with that change, thanks for implementing this!

Thanks, updated patch below.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch implements C++23 P2255R2, which adds two new type traits to
detect reference binding to a temporary.  They can be used to detect code
like

  std::tuple<const std::string&> t("meow");

which is incorrect because it always creates a dangling reference, because
the std::string temporary is created inside the selected constructor of
std::tuple, and not outside it.

There are two new compiler builtins, __reference_constructs_from_temporary
and __reference_converts_from_temporary.  The former is used to simulate
direct- and the latter copy-initialization context.  But I had a hard time
finding a test where there's actually a difference.  Under DR 2267, both
of these are invalid:

  struct A { } a;
  struct B { explicit B(const A&); };
  const B &b1{a};
  const B &b2(a);

so I had to peruse [over.match.ref], and eventually realized that the
difference can be seen here:

  struct G {
    operator int(); // #1
    explicit operator int&&(); // #2
  };

int&& r1(G{}); // use #2 (no temporary)
int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)

The implementation itself was rather straightforward because we already
have conv_binds_ref_to_prvalue.  The main function here is
reference_from_temporary.  The renaming to ref_conv_binds_to_temporary_p
is because previously the function didn't distinguish between an invalid
conversion and one that binds to a prvalue.

The patch also adds the relevant class and variable templates to <type_traits>.

	PR c++/104477

gcc/c-family/ChangeLog:

	* c-common.cc (c_common_reswords): Add
	__reference_constructs_from_temporary and
	__reference_converts_from_temporary.
	* c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
	RID_REF_CONVERTS_FROM_TEMPORARY.

gcc/cp/ChangeLog:

	* call.cc (ref_conv_binds_directly_p): Rename to ...
	(ref_conv_binds_to_temporary_p): ... this.  Add a new bool
	parameter.  Return true only if the conversion is valid and
	conv_binds_ref_to_prvalue returns true.
	* constraint.cc (diagnose_trait_expr): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	* cp-tree.h (enum cp_trait_kind): Add CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
	and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	(ref_conv_binds_directly_p): Rename to ...
	(ref_conv_binds_to_temporary_p): ... this.
	(reference_from_temporary): Declare.
	* cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	* method.cc (reference_from_temporary): New.
	* parser.cc (cp_parser_primary_expression): Handle
	RID_REF_CONSTRUCTS_FROM_TEMPORARY and RID_REF_CONVERTS_FROM_TEMPORARY.
	(cp_parser_trait_expr): Likewise.
	(warn_for_range_copy): Adjust to call ref_conv_binds_to_temporary_p.
	* semantics.cc (trait_expr_value): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	(finish_trait_expr): Likewise.

libstdc++-v3/ChangeLog:

	* include/std/type_traits (reference_constructs_from_temporary,
	reference_converts_from_temporary): New class templates.
	(reference_constructs_from_temporary_v,
	reference_converts_from_temporary_v): New variable templates.
	(__cpp_lib_reference_from_temporary): Define for C++23.
	* include/std/version (__cpp_lib_reference_from_temporary): Define for
	C++23.
	* testsuite/20_util/variable_templates_for_traits.cc: Test
	reference_constructs_from_temporary_v and
	reference_converts_from_temporary_v.
	* testsuite/20_util/reference_from_temporary/value.cc: New test.
	* testsuite/20_util/reference_from_temporary/value2.cc: New test.
	* testsuite/20_util/reference_from_temporary/version.cc: New test.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/reference_constructs_from_temporary1.C: New test.
	* g++.dg/ext/reference_converts_from_temporary1.C: New test.
---
 gcc/c-family/c-common.cc                      |   4 +
 gcc/c-family/c-common.h                       |   2 +
 gcc/cp/call.cc                                |  14 +-
 gcc/cp/constraint.cc                          |   8 +
 gcc/cp/cp-tree.h                              |   7 +-
 gcc/cp/cxx-pretty-print.cc                    |   6 +
 gcc/cp/method.cc                              |  28 +++
 gcc/cp/parser.cc                              |  14 +-
 gcc/cp/semantics.cc                           |   8 +
 .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
 .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
 libstdc++-v3/include/std/type_traits          |  39 ++++
 libstdc++-v3/include/std/version              |   5 +-
 .../20_util/reference_from_temporary/value.cc | 110 +++++++++
 .../reference_from_temporary/value2.cc        |  28 +++
 .../reference_from_temporary/version.cc       |  27 +++
 .../20_util/variable_templates_for_traits.cc  |  14 ++
 17 files changed, 730 insertions(+), 12 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 1b8e73f7bc5..655c3aefee6 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
   { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
   { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
   { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY },
+  { "__reference_constructs_from_temporary", RID_REF_CONSTRUCTS_FROM_TEMPORARY,
+					D_CXXONLY },
+  { "__reference_converts_from_temporary", RID_REF_CONVERTS_FROM_TEMPORARY,
+					D_CXXONLY },
 
   /* C++ transactional memory.  */
   { "synchronized",	RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c0900848965..f9064393b4e 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -184,6 +184,8 @@ enum rid
   RID_IS_UNION,                RID_UNDERLYING_TYPE,
   RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
   RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
+  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
+  RID_REF_CONVERTS_FROM_TEMPORARY,
 
   /* C++11 */
   RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index fc98552fda2..1ba209f61f1 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
   return conv_is_prvalue (next_conversion (c));
 }
 
-/* True iff converting EXPR to a reference type TYPE does not involve
-   creating a temporary.  */
+/* True iff converting EXPR to a reference type TYPE binds the reference to
+   a temporary.  DIRECT_INIT_P says whether the conversion should be done
+   in direct- or copy-initialization context.  */
 
 bool
-ref_conv_binds_directly_p (tree type, tree expr)
+ref_conv_binds_to_temporary_p (tree type, tree expr,
+			       bool direct_init_p /*= false*/)
 {
   gcc_assert (TYPE_REF_P (type));
 
   /* Get the high-water mark for the CONVERSION_OBSTACK.  */
   void *p = conversion_obstack_alloc (0);
 
+  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
   conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
-					  /*c_cast_p=*/false,
-					  LOOKUP_IMPLICIT, tf_none);
-  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
+					  /*c_cast_p=*/false, flags, tf_none);
+  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue (conv);
 
   /* Free all the conversions we allocated.  */
   obstack_free (&conversion_obstack, p);
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 591155cee22..648cc9d176d 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3687,6 +3687,14 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
       inform (loc, "  %qT does not have unique object representations", t1);
       break;
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      inform (loc, "  %qT is not a reference that binds to a temporary "
+	      "object of type %qT (direct-initialization)", t1, t2);
+      break;
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      inform (loc, "  %qT is not a reference that binds to a temporary "
+	      "object of type %qT (copy-initialization)", t1, t2);
+      break;
     case CPTK_BASES:
     case CPTK_DIRECT_BASES:
     case CPTK_UNDERLYING_TYPE:
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 2fde4f83b41..c3bed31e455 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1397,7 +1397,9 @@ enum cp_trait_kind
   CPTK_IS_ASSIGNABLE,
   CPTK_IS_CONSTRUCTIBLE,
   CPTK_IS_NOTHROW_ASSIGNABLE,
-  CPTK_IS_NOTHROW_CONSTRUCTIBLE
+  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
+  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
+  CPTK_REF_CONVERTS_FROM_TEMPORARY
 };
 
 /* The types that we are processing.  */
@@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p			(const_tree);
 extern tree type_decays_to			(tree);
 extern tree extract_call_expr			(tree);
 extern tree build_trivial_dtor_call		(tree, bool = false);
-extern bool ref_conv_binds_directly_p		(tree, tree);
+extern bool ref_conv_binds_to_temporary_p	(tree, tree, bool = false);
 extern tree build_user_type_conversion		(tree, tree, int,
 						 tsubst_flags_t);
 extern tree build_new_function_call		(tree, vec<tree, va_gc> **,
@@ -7105,6 +7107,7 @@ extern tree forward_parm			(tree);
 extern bool is_trivially_xible			(enum tree_code, tree, tree);
 extern bool is_nothrow_xible			(enum tree_code, tree, tree);
 extern bool is_xible				(enum tree_code, tree, tree);
+extern bool reference_from_temporary		(tree, tree, bool);
 extern tree get_defaulted_eh_spec		(tree, tsubst_flags_t = tf_warning_or_error);
 extern bool maybe_explain_implicit_delete	(tree);
 extern void explain_implicit_non_constexpr	(tree);
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 7e4db2e413b..44590830a61 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t)
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
       pp_cxx_ws_string (pp, "__is_nothrow_constructible");
       break;
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
+      break;
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
+      break;
 
     default:
       gcc_unreachable ();
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 0dffd648b0b..dd9715b6725 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to, tree from)
   return !!expr;
 }
 
+/* Return true iff conjunction_v<is_reference<T>, is_constructible<T, U>> is
+   true, and the initialization
+     T t(VAL<U>); // DIRECT_INIT_P
+   or
+     T t = VAL<U>; // !DIRECT_INIT_P
+   binds t to a temporary object whose lifetime is extended.
+   VAL<T> is defined in [meta.unary.prop]:
+   -- If T is a reference or function type, VAL<T> is an expression with the
+   same type and value category as declval<T>().
+   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
+
+bool
+reference_from_temporary (tree to, tree from, bool direct_init_p)
+{
+  /* Check is_reference<T>.  */
+  if (!TYPE_REF_P (to))
+    return false;
+  /* Check is_constructible<T, U>.
+     ??? This check doesn't seem to be necessary; if T isn't constructible
+     from U, we won't be able to create a conversion.  */
+  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
+    return false;
+  tree val = build_stub_object (from);
+  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
+    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) : rvalue (val);
+  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
+}
+
 /* Categorize various special_function_kinds.  */
 #define SFK_CTOR_P(sfk) \
   ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index bf9ea3685f8..edee94bda13 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
 	case RID_IS_CONSTRUCTIBLE:
 	case RID_IS_NOTHROW_ASSIGNABLE:
 	case RID_IS_NOTHROW_CONSTRUCTIBLE:
+	case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
+	case RID_REF_CONVERTS_FROM_TEMPORARY:
 	  return cp_parser_trait_expr (parser, token->keyword);
 
 	// C++ concepts
@@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
       kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
       variadic = true;
       break;
+    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
+      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
+      binary = true;
+      break;
+    case RID_REF_CONVERTS_FROM_TEMPORARY:
+      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
+      binary = true;
+      break;
     default:
       gcc_unreachable ();
     }
@@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
 
   if (TYPE_REF_P (type))
     {
-      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
+      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type, expr))
 	{
 	  auto_diagnostic_group d;
 	  if (warning_at (loc, OPT_Wrange_loop_construct,
@@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
   tree rtype = cp_build_reference_type (type, /*rval*/false);
   /* If we could initialize the reference directly, it wouldn't involve any
      copies.  */
-  if (!ref_conv_binds_directly_p (rtype, expr))
+  if (ref_conv_binds_to_temporary_p (rtype, expr))
     return;
 
   auto_diagnostic_group d;
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 2344b5eea00..396a9f6b44e 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -12007,6 +12007,12 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
       return is_nothrow_xible (INIT_EXPR, type1, type2);
 
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      return reference_from_temporary (type1, type2, /*direct_init=*/true);
+
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      return reference_from_temporary (type1, type2, /*direct_init=*/false);
+
     default:
       gcc_unreachable ();
       return false;
@@ -12088,6 +12094,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
     case CPTK_IS_NOTHROW_ASSIGNABLE:
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
       if (!check_trait_type (type1)
 	  || !check_trait_type (type2))
 	return error_mark_node;
diff --git a/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
new file mode 100644
index 00000000000..76de905a35d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
@@ -0,0 +1,214 @@
+// P2255R2
+// PR c++/104477
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+struct A { A(); };
+struct B { operator A(); };
+struct C { explicit C(); };
+struct D { explicit operator A(); };
+struct E { explicit operator A&(); };
+struct F { explicit operator A&&(); };
+// Could use a class template with explicit(bool), but then this would need
+// C++20.
+struct G {
+  operator int();
+  explicit operator int&&();
+};
+struct G2 {
+  explicit operator int();
+  operator int&&();
+};
+struct H {
+  operator int();
+  explicit operator int&();
+};
+struct H2 {
+  explicit operator int();
+  operator int&();
+};
+
+struct Base { };
+struct Der : Base { };
+
+template<typename T, typename RT>
+struct morph {
+  mutable T val{};
+  operator RT() const { return static_cast<RT>(val); }
+};
+
+template<typename T> using id = T;
+
+// Built-in types.
+SA(!__reference_constructs_from_temporary(int, int));
+SA(!__reference_constructs_from_temporary(int&, void));
+SA(!__reference_constructs_from_temporary(int&, const void));
+SA(!__reference_constructs_from_temporary(int&, volatile void));
+SA(!__reference_constructs_from_temporary(int&, const volatile void));
+SA(!__reference_constructs_from_temporary(void, void));
+SA(!__reference_constructs_from_temporary(void, int));
+SA(!__reference_constructs_from_temporary(int&, int));
+SA(!__reference_constructs_from_temporary(int&, int&));
+SA(!__reference_constructs_from_temporary(int&, int&&));
+SA(!__reference_constructs_from_temporary(int&, long));
+// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
+SA(!__reference_constructs_from_temporary(int&, long&));
+SA(!__reference_constructs_from_temporary(int&, long&&));
+SA( __reference_constructs_from_temporary(const int&, int));
+SA(!__reference_constructs_from_temporary(const int&, int&));
+SA(!__reference_constructs_from_temporary(const int&, const int&));
+SA(!__reference_constructs_from_temporary(const int&, int&&));
+SA( __reference_constructs_from_temporary(const int&, long));
+SA( __reference_constructs_from_temporary(const int&, long&));
+SA( __reference_constructs_from_temporary(const int&, long&&));
+SA( __reference_constructs_from_temporary(int&&, int));
+SA(!__reference_constructs_from_temporary(int&&, int&));
+SA(!__reference_constructs_from_temporary(int&&, int&&));
+SA( __reference_constructs_from_temporary(int&&, long));
+SA( __reference_constructs_from_temporary(int&&, long&));
+SA( __reference_constructs_from_temporary(int&&, long&&));
+SA(!__reference_constructs_from_temporary(unsigned int&, double));
+SA(!__reference_constructs_from_temporary(volatile int&, int));
+SA(!__reference_constructs_from_temporary(const volatile int&, int));
+SA(!__reference_constructs_from_temporary(volatile int&, int&));
+SA(!__reference_constructs_from_temporary(const volatile int&, int&));
+SA(!__reference_constructs_from_temporary(volatile int&, int&&));
+SA(!__reference_constructs_from_temporary(const volatile int&, int&&));
+
+// Classes.
+SA(!__reference_constructs_from_temporary(A, A));
+// A& r(A{}); doesn't construct.
+SA(!__reference_constructs_from_temporary(A&, A));
+SA(!__reference_constructs_from_temporary(A&, A&));
+SA(!__reference_constructs_from_temporary(A&, A&&));
+// Here we get const struct A & r = (const struct A &) &D.2414;
+SA( __reference_constructs_from_temporary(const A&, A));
+SA(!__reference_constructs_from_temporary(const A&, A&));
+SA(!__reference_constructs_from_temporary(const A&, const A&));
+SA(!__reference_constructs_from_temporary(const A&, A&&));
+// Here we get struct A & r = (struct A &) &D.2439;
+SA( __reference_constructs_from_temporary(A&&, A));
+SA(!__reference_constructs_from_temporary(A&&, A&));
+SA(!__reference_constructs_from_temporary(A&&, const A&));
+SA(!__reference_constructs_from_temporary(A&&, A&&));
+
+SA(!__reference_constructs_from_temporary(A, B));
+SA(!__reference_constructs_from_temporary(A&, B));
+SA(!__reference_constructs_from_temporary(A&, B&));
+SA(!__reference_constructs_from_temporary(A&, const B&));
+SA(!__reference_constructs_from_temporary(A&, B&&));
+SA( __reference_constructs_from_temporary(const A&, B));
+SA( __reference_constructs_from_temporary(const A&, B&));
+// Doesn't construct, so it's false.
+SA(!__reference_constructs_from_temporary(const A&, const B&));
+SA( __reference_constructs_from_temporary(const A&, B&&));
+SA( __reference_constructs_from_temporary(A&&, B));
+SA( __reference_constructs_from_temporary(A&&, B&));
+SA(!__reference_constructs_from_temporary(A&&, const B&));
+SA( __reference_constructs_from_temporary(A&&, B&&));
+
+SA(!__reference_constructs_from_temporary(const A&, C));
+SA(!__reference_constructs_from_temporary(const A&, C&));
+
+// Doesn't construct, so it's false.
+SA(!__reference_constructs_from_temporary(int&, morph<int, int>));
+// Here we get
+//   const int & r2 = D.2580 = morph<int, int>::operator int
+//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
+SA( __reference_constructs_from_temporary(const int&, morph<int, int>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, int&>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, const int&>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, int&&>));
+SA( __reference_constructs_from_temporary(const int&, morph<long, long&>));
+
+// These are like const C& c(cref); so the explicit ctor C isn't a problem
+// even in copy-init context.  const C& r = {}; would be a different story.
+SA(!__reference_constructs_from_temporary(C, C));
+SA(!__reference_constructs_from_temporary(C&, C));
+SA(!__reference_constructs_from_temporary(C&, C&));
+SA(!__reference_constructs_from_temporary(C&, C&&));
+SA( __reference_constructs_from_temporary(const C&, C));
+SA(!__reference_constructs_from_temporary(const C&, C&));
+SA(!__reference_constructs_from_temporary(const C&, const C&));
+SA(!__reference_constructs_from_temporary(const C&, C&&));
+SA( __reference_constructs_from_temporary(C&&, C));
+SA(!__reference_constructs_from_temporary(C&&, C&));
+SA(!__reference_constructs_from_temporary(C&&, const C&));
+SA(!__reference_constructs_from_temporary(C&&, C&&));
+
+// These are all false ultimately because of CWG 2267, which we implement.
+SA(!__reference_constructs_from_temporary(A, D));
+SA(!__reference_constructs_from_temporary(A&, D));
+SA(!__reference_constructs_from_temporary(A&, D&));
+SA(!__reference_constructs_from_temporary(A&, const D&));
+SA(!__reference_constructs_from_temporary(A&, D&&));
+SA(!__reference_constructs_from_temporary(const A&, D));
+SA(!__reference_constructs_from_temporary(const A&, D&));
+SA(!__reference_constructs_from_temporary(const A&, const D&));
+SA(!__reference_constructs_from_temporary(const A&, D&&));
+SA(!__reference_constructs_from_temporary(A&&, D));
+SA(!__reference_constructs_from_temporary(A&&, D&));
+SA(!__reference_constructs_from_temporary(A&&, const D&));
+SA(!__reference_constructs_from_temporary(A&&, D&&));
+
+SA(!__reference_constructs_from_temporary(A, E));
+/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
+   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
+   so we're not binding the reference to a temporary, although there is
+   a temporary involved.  So the result is false in both copy- and direct-
+   init, albeit for different reasons!  */
+SA(!__reference_constructs_from_temporary(A&, E));
+// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
+SA(!__reference_constructs_from_temporary(A&, E&));
+SA(!__reference_constructs_from_temporary(A&, const E&));
+SA(!__reference_constructs_from_temporary(A&, E&&));
+// direct-init:
+// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
+SA(!__reference_constructs_from_temporary(const A&, E));
+SA(!__reference_constructs_from_temporary(const A&, E&));
+SA(!__reference_constructs_from_temporary(const A&, const E&));
+SA(!__reference_constructs_from_temporary(const A&, E&&));
+SA(!__reference_constructs_from_temporary(A&&, E));
+SA(!__reference_constructs_from_temporary(A&&, E&));
+SA(!__reference_constructs_from_temporary(A&&, const E&));
+SA(!__reference_constructs_from_temporary(A&&, E&&));
+
+SA(!__reference_constructs_from_temporary(A, F));
+// A& a1(F{}); and A& a2 = F{}; both invalid.
+SA(!__reference_constructs_from_temporary(A&, F));
+SA(!__reference_constructs_from_temporary(A&, F&));
+SA(!__reference_constructs_from_temporary(A&, const F&));
+SA(!__reference_constructs_from_temporary(A&, F&&));
+SA(!__reference_constructs_from_temporary(const A&, F));
+SA(!__reference_constructs_from_temporary(const A&, F&));
+SA(!__reference_constructs_from_temporary(const A&, const F&));
+SA(!__reference_constructs_from_temporary(const A&, F&&));
+SA(!__reference_constructs_from_temporary(A&&, F));
+SA(!__reference_constructs_from_temporary(A&&, F&));
+SA(!__reference_constructs_from_temporary(A&&, const F&));
+SA(!__reference_constructs_from_temporary(A&&, F&&));
+
+/* This is where _converts_ and _constructs_ will differ:
+   in direct-init we use G::operator int&& (no temporary),
+   but in copy-init we use G::operator int, where a temporary is created
+   to be bound to int&&.  */
+SA(!__reference_constructs_from_temporary(int&&, G));
+// Similar to the previous one.
+SA(!__reference_constructs_from_temporary(const int&, H));
+/* And here I've switched the explicit-ness.  In both copy- and direct-init
+   we call operator int&, so no temporary.  */
+SA(!__reference_constructs_from_temporary(int&&, G2));
+SA(!__reference_constructs_from_temporary(const int&, H2));
+
+SA(!__reference_constructs_from_temporary(const Base&, Der));
+
+// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
+SA(!__reference_constructs_from_temporary(int&&, id<int[3]>));
+
+// Arrays.
+SA(!__reference_constructs_from_temporary(int, int[]));
+SA(!__reference_constructs_from_temporary(int[], int[]));
+SA(!__reference_constructs_from_temporary(int&, int[]));
+SA(!__reference_constructs_from_temporary(int&&, int[]));
+SA(!__reference_constructs_from_temporary(const int&, int[]));
diff --git a/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
new file mode 100644
index 00000000000..90196c38742
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
@@ -0,0 +1,214 @@
+// P2255R2
+// PR c++/104477
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+struct A { A(); };
+struct B { operator A(); };
+struct C { explicit C(); };
+struct D { explicit operator A(); };
+struct E { explicit operator A&(); };
+struct F { explicit operator A&&(); };
+// Could use a class template with explicit(bool), but then this would need
+// C++20.
+struct G {
+  operator int();
+  explicit operator int&&();
+};
+struct G2 {
+  explicit operator int();
+  operator int&&();
+};
+struct H {
+  operator int();
+  explicit operator int&();
+};
+struct H2 {
+  explicit operator int();
+  operator int&();
+};
+
+struct Base { };
+struct Der : Base { };
+
+template<typename T, typename RT>
+struct morph {
+  mutable T val{};
+  operator RT() const { return static_cast<RT>(val); }
+};
+
+template<typename T> using id = T;
+
+// Built-in types.
+SA(!__reference_converts_from_temporary(int, int));
+SA(!__reference_converts_from_temporary(int&, void));
+SA(!__reference_converts_from_temporary(int&, const void));
+SA(!__reference_converts_from_temporary(int&, volatile void));
+SA(!__reference_converts_from_temporary(int&, const volatile void));
+SA(!__reference_converts_from_temporary(void, void));
+SA(!__reference_converts_from_temporary(void, int));
+SA(!__reference_converts_from_temporary(int&, int));
+SA(!__reference_converts_from_temporary(int&, int&));
+SA(!__reference_converts_from_temporary(int&, int&&));
+SA(!__reference_converts_from_temporary(int&, long));
+// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
+SA(!__reference_converts_from_temporary(int&, long&));
+SA(!__reference_converts_from_temporary(int&, long&&));
+SA( __reference_converts_from_temporary(const int&, int));
+SA(!__reference_converts_from_temporary(const int&, int&));
+SA(!__reference_converts_from_temporary(const int&, const int&));
+SA(!__reference_converts_from_temporary(const int&, int&&));
+SA( __reference_converts_from_temporary(const int&, long));
+SA( __reference_converts_from_temporary(const int&, long&));
+SA( __reference_converts_from_temporary(const int&, long&&));
+SA( __reference_converts_from_temporary(int&&, int));
+SA(!__reference_converts_from_temporary(int&&, int&));
+SA(!__reference_converts_from_temporary(int&&, int&&));
+SA( __reference_converts_from_temporary(int&&, long));
+SA( __reference_converts_from_temporary(int&&, long&));
+SA( __reference_converts_from_temporary(int&&, long&&));
+SA(!__reference_converts_from_temporary(unsigned int&, double));
+SA(!__reference_converts_from_temporary(volatile int&, int));
+SA(!__reference_converts_from_temporary(const volatile int&, int));
+SA(!__reference_converts_from_temporary(volatile int&, int&));
+SA(!__reference_converts_from_temporary(const volatile int&, int&));
+SA(!__reference_converts_from_temporary(volatile int&, int&&));
+SA(!__reference_converts_from_temporary(const volatile int&, int&&));
+
+// Classes.
+SA(!__reference_converts_from_temporary(A, A));
+// A& r(A{}); doesn't construct.
+SA(!__reference_converts_from_temporary(A&, A));
+SA(!__reference_converts_from_temporary(A&, A&));
+SA(!__reference_converts_from_temporary(A&, A&&));
+// Here we get const struct A & r = (const struct A &) &D.2414;
+SA( __reference_converts_from_temporary(const A&, A));
+SA(!__reference_converts_from_temporary(const A&, A&));
+SA(!__reference_converts_from_temporary(const A&, const A&));
+SA(!__reference_converts_from_temporary(const A&, A&&));
+// Here we get struct A & r = (struct A &) &D.2439;
+SA( __reference_converts_from_temporary(A&&, A));
+SA(!__reference_converts_from_temporary(A&&, A&));
+SA(!__reference_converts_from_temporary(A&&, const A&));
+SA(!__reference_converts_from_temporary(A&&, A&&));
+
+SA(!__reference_converts_from_temporary(A, B));
+SA(!__reference_converts_from_temporary(A&, B));
+SA(!__reference_converts_from_temporary(A&, B&));
+SA(!__reference_converts_from_temporary(A&, const B&));
+SA(!__reference_converts_from_temporary(A&, B&&));
+SA( __reference_converts_from_temporary(const A&, B));
+SA( __reference_converts_from_temporary(const A&, B&));
+// Doesn't construct, so it's false.
+SA(!__reference_converts_from_temporary(const A&, const B&));
+SA( __reference_converts_from_temporary(const A&, B&&));
+SA( __reference_converts_from_temporary(A&&, B));
+SA( __reference_converts_from_temporary(A&&, B&));
+SA(!__reference_converts_from_temporary(A&&, const B&));
+SA( __reference_converts_from_temporary(A&&, B&&));
+
+SA(!__reference_converts_from_temporary(const A&, C));
+SA(!__reference_converts_from_temporary(const A&, C&));
+
+// Doesn't construct, so it's false.
+SA(!__reference_converts_from_temporary(int&, morph<int, int>));
+// Here we get
+//   const int & r2 = D.2580 = morph<int, int>::operator int
+//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
+SA( __reference_converts_from_temporary(const int&, morph<int, int>));
+SA(!__reference_converts_from_temporary(int&, morph<int, int&>));
+SA(!__reference_converts_from_temporary(int&, morph<int, const int&>));
+SA(!__reference_converts_from_temporary(int&, morph<int, int&&>));
+SA( __reference_converts_from_temporary(const int&, morph<long, long&>));
+
+// These are like const C& c(cref); so the explicit ctor C isn't a problem
+// even in copy-init context.  const C& r = {}; would be a different story.
+SA(!__reference_converts_from_temporary(C, C));
+SA(!__reference_converts_from_temporary(C&, C));
+SA(!__reference_converts_from_temporary(C&, C&));
+SA(!__reference_converts_from_temporary(C&, C&&));
+SA( __reference_converts_from_temporary(const C&, C));
+SA(!__reference_converts_from_temporary(const C&, C&));
+SA(!__reference_converts_from_temporary(const C&, const C&));
+SA(!__reference_converts_from_temporary(const C&, C&&));
+SA( __reference_converts_from_temporary(C&&, C));
+SA(!__reference_converts_from_temporary(C&&, C&));
+SA(!__reference_converts_from_temporary(C&&, const C&));
+SA(!__reference_converts_from_temporary(C&&, C&&));
+
+// These are all false ultimately because of CWG 2267, which we implement.
+SA(!__reference_converts_from_temporary(A, D));
+SA(!__reference_converts_from_temporary(A&, D));
+SA(!__reference_converts_from_temporary(A&, D&));
+SA(!__reference_converts_from_temporary(A&, const D&));
+SA(!__reference_converts_from_temporary(A&, D&&));
+SA(!__reference_converts_from_temporary(const A&, D));
+SA(!__reference_converts_from_temporary(const A&, D&));
+SA(!__reference_converts_from_temporary(const A&, const D&));
+SA(!__reference_converts_from_temporary(const A&, D&&));
+SA(!__reference_converts_from_temporary(A&&, D));
+SA(!__reference_converts_from_temporary(A&&, D&));
+SA(!__reference_converts_from_temporary(A&&, const D&));
+SA(!__reference_converts_from_temporary(A&&, D&&));
+
+SA(!__reference_converts_from_temporary(A, E));
+/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
+   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
+   so we're not binding the reference to a temporary, although there is
+   a temporary involved.  So the result is false in both copy- and direct-
+   init, albeit for different reasons!  */
+SA(!__reference_converts_from_temporary(A&, E));
+// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
+SA(!__reference_converts_from_temporary(A&, E&));
+SA(!__reference_converts_from_temporary(A&, const E&));
+SA(!__reference_converts_from_temporary(A&, E&&));
+// direct-init:
+// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
+SA(!__reference_converts_from_temporary(const A&, E));
+SA(!__reference_converts_from_temporary(const A&, E&));
+SA(!__reference_converts_from_temporary(const A&, const E&));
+SA(!__reference_converts_from_temporary(const A&, E&&));
+SA(!__reference_converts_from_temporary(A&&, E));
+SA(!__reference_converts_from_temporary(A&&, E&));
+SA(!__reference_converts_from_temporary(A&&, const E&));
+SA(!__reference_converts_from_temporary(A&&, E&&));
+
+SA(!__reference_converts_from_temporary(A, F));
+// A& a1(F{}); and A& a2 = F{}; both invalid.
+SA(!__reference_converts_from_temporary(A&, F));
+SA(!__reference_converts_from_temporary(A&, F&));
+SA(!__reference_converts_from_temporary(A&, const F&));
+SA(!__reference_converts_from_temporary(A&, F&&));
+SA(!__reference_converts_from_temporary(const A&, F));
+SA(!__reference_converts_from_temporary(const A&, F&));
+SA(!__reference_converts_from_temporary(const A&, const F&));
+SA(!__reference_converts_from_temporary(const A&, F&&));
+SA(!__reference_converts_from_temporary(A&&, F));
+SA(!__reference_converts_from_temporary(A&&, F&));
+SA(!__reference_converts_from_temporary(A&&, const F&));
+SA(!__reference_converts_from_temporary(A&&, F&&));
+
+/* This is where _converts_ and _constructs_ will differ:
+   in direct-init we use G::operator int&& (no temporary),
+   but in copy-init we use G::operator int, where a temporary is created
+   to be bound to int&&.  */
+SA( __reference_converts_from_temporary(int&&, G));
+// Similar to the previous one.
+SA( __reference_converts_from_temporary(const int&, H));
+/* And here I've switched the explicit-ness.  In both copy- and direct-init
+   we call operator int&, so no temporary.  */
+SA(!__reference_converts_from_temporary(int&&, G2));
+SA(!__reference_converts_from_temporary(const int&, H2));
+
+SA(!__reference_converts_from_temporary(const Base&, Der));
+
+// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
+SA(!__reference_converts_from_temporary(int&&, id<int[3]>));
+
+// Arrays.
+SA(!__reference_converts_from_temporary(int, int[]));
+SA(!__reference_converts_from_temporary(int[], int[]));
+SA(!__reference_converts_from_temporary(int&, int[]));
+SA(!__reference_converts_from_temporary(int&&, int[]));
+SA(!__reference_converts_from_temporary(const int&, int[]));
diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index e5f58bc2e3f..b1a1deecf66 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -3505,6 +3505,45 @@ template<typename _Ret, typename _Fn, typename... _Args>
   template<typename _Tp>
     inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
 
+#define __cpp_lib_reference_from_temporary 202202L
+
+  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
+  /// direct-initialization, and a temporary object would be bound to
+  /// the reference, false otherwise.
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    struct reference_constructs_from_temporary
+    : public bool_constant<__reference_constructs_from_temporary(_Tp, _Up)>
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
+		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
+	"template argument must be a complete class or an unbounded array");
+    };
+
+  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
+  /// copy-initialization, and a temporary object would be bound to
+  /// the reference, false otherwise.
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    struct reference_converts_from_temporary
+    : public bool_constant<__reference_converts_from_temporary(_Tp, _Up)>
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
+		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
+	"template argument must be a complete class or an unbounded array");
+    };
+
+  /// @ingroup variable_templates
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    inline constexpr bool reference_constructs_from_temporary_v
+      = reference_constructs_from_temporary<_Tp, _Up>::value;
+
+  /// @ingroup variable_templates
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    inline constexpr bool reference_converts_from_temporary_v
+      = reference_converts_from_temporary<_Tp, _Up>::value;
 #endif // C++23
 
 #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
index 22280e1a349..5edca2f3007 100644
--- a/libstdc++-v3/include/std/version
+++ b/libstdc++-v3/include/std/version
@@ -300,10 +300,11 @@
 #endif
 
 #if __cplusplus > 202002L
-// c++2b
+// c++23
 #define __cpp_lib_byteswap 202110L
 #define __cpp_lib_constexpr_typeinfo 202106L
 #define __cpp_lib_is_scoped_enum 202011L
+#define __cpp_lib_reference_from_temporary 202202L
 
 #if _GLIBCXX_HOSTED
 #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
@@ -335,7 +336,7 @@
 #define __cpp_lib_to_underlying 202102L
 #define __cpp_lib_unreachable 202202L
 #endif
-#endif // C++2b
+#endif // C++23
 #endif // C++20
 #endif // C++17
 #endif // C++14
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
new file mode 100644
index 00000000000..2f62e54d46d
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
@@ -0,0 +1,110 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <type_traits>
+#include <testsuite_tr1.h>
+
+#ifndef __cpp_lib_reference_from_temporary
+# error "Feature test macro for reference_from_temporary is missing in <version>"
+#elif __cpp_lib_reference_from_temporary < 202202L
+# error "Feature test macro for reference_from_temporary has wrong value in <version>"
+#endif
+
+void test01()
+{
+  using std::reference_constructs_from_temporary;
+  using std::reference_converts_from_temporary;
+  using namespace __gnu_test;
+
+  struct A { A(); };
+
+  struct B {
+    operator int();
+    explicit operator int&&();
+  };
+
+  struct C {
+    operator int();
+    explicit operator int&();
+  };
+
+  static_assert(test_property<reference_constructs_from_temporary, int, int>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, const volatile void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, void, void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long&&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long&&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const A&, A>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const A&, A&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, A&&, A>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int[]>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int[]>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int[]>(false), "");
+
+  static_assert(test_property<reference_converts_from_temporary, int, int>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, const volatile void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, void, void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long&&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long&&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const A&, A>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const A&, A&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, A&&, A>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int[]>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int[]>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int[]>(false), "");
+
+  static_assert(test_property<reference_constructs_from_temporary, int&&, B>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, C>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, B>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, C>(true), "");
+}
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
new file mode 100644
index 00000000000..65770754299
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <type_traits>
+#include <string>
+
+void test01()
+{
+  static_assert(std::reference_converts_from_temporary_v<const std::string&, const char*>);
+  static_assert(std::reference_constructs_from_temporary_v<const std::string&, const char*>);
+}
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
new file mode 100644
index 00000000000..f56e7c0dabc
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <version>
+
+#ifndef __cpp_lib_reference_from_temporary
+# error "Feature test macro for reference_from_temporary is missing in <version>"
+#elif __cpp_lib_reference_from_temporary < 202202L
+# error "Feature test macro for reference_from_temporary has wrong value in <version>"
+#endif
diff --git a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
index 9b3957f7d47..2b03ad7067d 100644
--- a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
+++ b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
@@ -346,3 +346,17 @@ static_assert(disjunction_v<false_type, false_type,
               true_type>, "");
 static_assert(!disjunction_v<false_type, false_type,
               false_type>, "");
+#if __cpp_lib_reference_from_temporary >= 202202L
+static_assert(std::reference_converts_from_temporary_v<int&&, int>
+	      && std::reference_converts_from_temporary_v<const int&, int>
+	      && !std::reference_converts_from_temporary_v<int&&, int&&>
+	      && !std::reference_converts_from_temporary_v<const int&, int&&>
+	      && std::reference_converts_from_temporary_v<int&&, long&&>
+	      && std::reference_converts_from_temporary_v<int&&, long>, "");
+static_assert(std::reference_constructs_from_temporary_v<int&&, int>
+	      && std::reference_constructs_from_temporary_v<const int&, int>
+	      && !std::reference_constructs_from_temporary_v<int&&, int&&>
+	      && !std::reference_constructs_from_temporary_v<const int&, int&&>
+	      && std::reference_constructs_from_temporary_v<int&&, long&&>
+	      && std::reference_constructs_from_temporary_v<int&&, long>, "");
+#endif

base-commit: cab411a2b4b4f6a6b619d0650fade85288a31f9e
-- 
2.36.1


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-08 17:41 [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Marek Polacek
  2022-07-11 10:19 ` Jonathan Wakely
@ 2022-07-12 20:10 ` Jason Merrill
  2022-07-12 20:15   ` Jason Merrill
  2022-07-14 17:41   ` [PATCH v3] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
  1 sibling, 2 replies; 15+ messages in thread
From: Jason Merrill @ 2022-07-12 20:10 UTC (permalink / raw)
  To: Marek Polacek, GCC Patches, libstdc++, Jonathan Wakely

On 7/8/22 13:41, Marek Polacek wrote:
> This patch implements C++23 P2255R2, which adds two new type traits to
> detect reference binding to a temporary.  They can be used to detect code
> like
> 
>    std::tuple<const std::string&> t("meow");
> 
> which is incorrect because it always creates a dangling reference, because
> the std::string temporary is created inside the selected constructor of
> std::tuple, and not outside it.
> 
> There are two new compiler builtins, __reference_constructs_from_temporary
> and __reference_converts_from_temporary.  The former is used to simulate
> direct- and the latter copy-initialization context.  But I had a hard time
> finding a test where there's actually a difference.  Under DR 2267, both
> of these are invalid:
> 
>    struct A { } a;
>    struct B { explicit B(const A&); };
>    const B &b1{a};
>    const B &b2(a);
> 
> so I had to peruse [over.match.ref], and eventually realized that the
> difference can be seen here:
> 
>    struct G {
>      operator int(); // #1
>      explicit operator int&&(); // #2
>    };
> 
> int&& r1(G{}); // use #2 (no temporary)
> int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)
> 
> The implementation itself was rather straightforward because we already
> have conv_binds_ref_to_prvalue.  The main function here is
> reference_from_temporary.  The renaming to ref_conv_binds_to_temporary_p
> is because previously the function didn't distinguish between an invalid
> conversion and one that binds to a prvalue.
> 
> The patch also adds the relevant class and variable templates to <type_traits>.
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> 	PR c++/104477
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-common.cc (c_common_reswords): Add
> 	__reference_constructs_from_temporary and
> 	__reference_converts_from_temporary.
> 	* c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> 	RID_REF_CONVERTS_FROM_TEMPORARY.
> 
> gcc/cp/ChangeLog:
> 
> 	* call.cc (ref_conv_binds_directly_p): Rename to ...
> 	(ref_conv_binds_to_temporary_p): ... this.  Add a new bool
> 	parameter.  Return true only if the conversion is valid and
> 	conv_binds_ref_to_prvalue returns true.
> 	* constraint.cc (diagnose_trait_expr): Handle
> 	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
> 	* cp-tree.h (enum cp_trait_kind): Add CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
> 	and CPTK_REF_CONVERTS_FROM_TEMPORARY.
> 	(ref_conv_binds_directly_p): Rename to ...
> 	(ref_conv_binds_to_temporary_p): ... this.
> 	(reference_from_temporary): Declare.
> 	* cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
> 	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
> 	* method.cc (reference_from_temporary): New.
> 	* parser.cc (cp_parser_primary_expression): Handle
> 	RID_REF_CONSTRUCTS_FROM_TEMPORARY and RID_REF_CONVERTS_FROM_TEMPORARY.
> 	(cp_parser_trait_expr): Likewise.
> 	(warn_for_range_copy): Adjust to call ref_conv_binds_to_temporary_p.
> 	* semantics.cc (trait_expr_value): Handle
> 	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
> 	(finish_trait_expr): Likewise.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/std/type_traits (reference_constructs_from_temporary,
> 	reference_converts_from_temporary): New class templates.
> 	(reference_constructs_from_temporary_v,
> 	reference_converts_from_temporary_v): New variable templates.
> 	(__cpp_lib_reference_from_temporary): Define for C++23.
> 	* include/std/version (__cpp_lib_reference_from_temporary): Define for
> 	C++23.
> 	* testsuite/20_util/variable_templates_for_traits.cc: Test
> 	reference_constructs_from_temporary_v and
> 	reference_converts_from_temporary_v.
> 	* testsuite/20_util/reference_from_temporary/value.cc: New test.
> 	* testsuite/20_util/reference_from_temporary/value2.cc: New test.
> 	* testsuite/20_util/reference_from_temporary/version.cc: New test.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/reference_constructs_from_temporary1.C: New test.
> 	* g++.dg/ext/reference_converts_from_temporary1.C: New test.
> ---
>   gcc/c-family/c-common.cc                      |   4 +
>   gcc/c-family/c-common.h                       |   2 +
>   gcc/cp/call.cc                                |  14 +-
>   gcc/cp/constraint.cc                          |   8 +
>   gcc/cp/cp-tree.h                              |   7 +-
>   gcc/cp/cxx-pretty-print.cc                    |   6 +
>   gcc/cp/method.cc                              |  28 +++
>   gcc/cp/parser.cc                              |  14 +-
>   gcc/cp/semantics.cc                           |   8 +
>   .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
>   .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
>   libstdc++-v3/include/std/type_traits          |  39 ++++
>   libstdc++-v3/include/std/version              |   5 +-
>   .../20_util/reference_from_temporary/value.cc | 110 +++++++++
>   .../reference_from_temporary/value2.cc        |  28 +++
>   .../reference_from_temporary/version.cc       |  27 +++
>   .../20_util/variable_templates_for_traits.cc  |  14 ++
>   17 files changed, 730 insertions(+), 12 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
>   create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
>   create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
>   create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> 
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index 1b8e73f7bc5..655c3aefee6 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
>     { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
>     { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
>     { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY },
> +  { "__reference_constructs_from_temporary", RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> +					D_CXXONLY },
> +  { "__reference_converts_from_temporary", RID_REF_CONVERTS_FROM_TEMPORARY,
> +					D_CXXONLY },
>   
>     /* C++ transactional memory.  */
>     { "synchronized",	RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index c0900848965..f9064393b4e 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -184,6 +184,8 @@ enum rid
>     RID_IS_UNION,                RID_UNDERLYING_TYPE,
>     RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
>     RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
> +  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> +  RID_REF_CONVERTS_FROM_TEMPORARY,
>   
>     /* C++11 */
>     RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index fc98552fda2..1ba209f61f1 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
>     return conv_is_prvalue (next_conversion (c));
>   }
>   
> -/* True iff converting EXPR to a reference type TYPE does not involve
> -   creating a temporary.  */
> +/* True iff converting EXPR to a reference type TYPE binds the reference to
> +   a temporary.  DIRECT_INIT_P says whether the conversion should be done
> +   in direct- or copy-initialization context.  */
>   
>   bool
> -ref_conv_binds_directly_p (tree type, tree expr)
> +ref_conv_binds_to_temporary_p (tree type, tree expr,
> +			       bool direct_init_p /*= false*/)
>   {
>     gcc_assert (TYPE_REF_P (type));
>   
>     /* Get the high-water mark for the CONVERSION_OBSTACK.  */
>     void *p = conversion_obstack_alloc (0);
>   
> +  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
>     conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
> -					  /*c_cast_p=*/false,
> -					  LOOKUP_IMPLICIT, tf_none);
> -  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
> +					  /*c_cast_p=*/false, flags, tf_none);
> +  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue (conv);
>   
>     /* Free all the conversions we allocated.  */
>     obstack_free (&conversion_obstack, p);
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 591155cee22..648cc9d176d 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -3687,6 +3687,14 @@ diagnose_trait_expr (tree expr, tree args)
>       case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
>         inform (loc, "  %qT does not have unique object representations", t1);
>         break;
> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> +      inform (loc, "  %qT is not a reference that binds to a temporary "
> +	      "object of type %qT (direct-initialization)", t1, t2);
> +      break;
> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> +      inform (loc, "  %qT is not a reference that binds to a temporary "
> +	      "object of type %qT (copy-initialization)", t1, t2);
> +      break;
>       case CPTK_BASES:
>       case CPTK_DIRECT_BASES:
>       case CPTK_UNDERLYING_TYPE:
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 2fde4f83b41..c3bed31e455 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1397,7 +1397,9 @@ enum cp_trait_kind
>     CPTK_IS_ASSIGNABLE,
>     CPTK_IS_CONSTRUCTIBLE,
>     CPTK_IS_NOTHROW_ASSIGNABLE,
> -  CPTK_IS_NOTHROW_CONSTRUCTIBLE
> +  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
> +  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
> +  CPTK_REF_CONVERTS_FROM_TEMPORARY
>   };
>   
>   /* The types that we are processing.  */
> @@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p			(const_tree);
>   extern tree type_decays_to			(tree);
>   extern tree extract_call_expr			(tree);
>   extern tree build_trivial_dtor_call		(tree, bool = false);
> -extern bool ref_conv_binds_directly_p		(tree, tree);
> +extern bool ref_conv_binds_to_temporary_p	(tree, tree, bool = false);
>   extern tree build_user_type_conversion		(tree, tree, int,
>   						 tsubst_flags_t);
>   extern tree build_new_function_call		(tree, vec<tree, va_gc> **,
> @@ -7105,6 +7107,7 @@ extern tree forward_parm			(tree);
>   extern bool is_trivially_xible			(enum tree_code, tree, tree);
>   extern bool is_nothrow_xible			(enum tree_code, tree, tree);
>   extern bool is_xible				(enum tree_code, tree, tree);
> +extern bool reference_from_temporary		(tree, tree, bool);
>   extern tree get_defaulted_eh_spec		(tree, tsubst_flags_t = tf_warning_or_error);
>   extern bool maybe_explain_implicit_delete	(tree);
>   extern void explain_implicit_non_constexpr	(tree);
> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> index 7e4db2e413b..44590830a61 100644
> --- a/gcc/cp/cxx-pretty-print.cc
> +++ b/gcc/cp/cxx-pretty-print.cc
> @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t)
>       case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
>         pp_cxx_ws_string (pp, "__is_nothrow_constructible");
>         break;
> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> +      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
> +      break;
> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> +      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
> +      break;
>   
>       default:
>         gcc_unreachable ();
> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> index 0dffd648b0b..dd9715b6725 100644
> --- a/gcc/cp/method.cc
> +++ b/gcc/cp/method.cc
> @@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to, tree from)
>     return !!expr;
>   }
>   
> +/* Return true iff conjunction_v<is_reference<T>, is_constructible<T, U>> is
> +   true, and the initialization
> +     T t(VAL<U>); // DIRECT_INIT_P
> +   or
> +     T t = VAL<U>; // !DIRECT_INIT_P
> +   binds t to a temporary object whose lifetime is extended.
> +   VAL<T> is defined in [meta.unary.prop]:
> +   -- If T is a reference or function type, VAL<T> is an expression with the
> +   same type and value category as declval<T>().
> +   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
> +
> +bool
> +reference_from_temporary (tree to, tree from, bool direct_init_p)
> +{
> +  /* Check is_reference<T>.  */
> +  if (!TYPE_REF_P (to))
> +    return false;
> +  /* Check is_constructible<T, U>.
> +     ??? This check doesn't seem to be necessary; if T isn't constructible
> +     from U, we won't be able to create a conversion.  */
> +  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
> +    return false;

I agree with the comment, did you try leaving this out?  If it stays I'd 
think it needs to consider direct_init_p.

> +  tree val = build_stub_object (from);
> +  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
> +    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) : rvalue (val);
> +  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
> +}
> +
>   /* Categorize various special_function_kinds.  */
>   #define SFK_CTOR_P(sfk) \
>     ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index bf9ea3685f8..edee94bda13 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
>   	case RID_IS_CONSTRUCTIBLE:
>   	case RID_IS_NOTHROW_ASSIGNABLE:
>   	case RID_IS_NOTHROW_CONSTRUCTIBLE:
> +	case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> +	case RID_REF_CONVERTS_FROM_TEMPORARY:
>   	  return cp_parser_trait_expr (parser, token->keyword);
>   
>   	// C++ concepts
> @@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
>         kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
>         variadic = true;
>         break;
> +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> +      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
> +      binary = true;
> +      break;
> +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> +      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
> +      binary = true;
> +      break;
>       default:
>         gcc_unreachable ();
>       }
> @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
>   
>     if (TYPE_REF_P (type))
>       {
> -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
> +      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type, expr))
>   	{
>   	  auto_diagnostic_group d;
>   	  if (warning_at (loc, OPT_Wrange_loop_construct,
> @@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
>     tree rtype = cp_build_reference_type (type, /*rval*/false);
>     /* If we could initialize the reference directly, it wouldn't involve any
>        copies.  */
> -  if (!ref_conv_binds_directly_p (rtype, expr))
> +  if (ref_conv_binds_to_temporary_p (rtype, expr))
>       return;

I think this case wants the old handling of invalid conversions you 
mentioned in your intro; we don't want to suggest changing to a 
reference if that's ill-formed.

In passing we might change the comment to "If we can initialize a 
reference directly, suggest that to avoid the copy." and move it above 
the rtype declaration.

>     auto_diagnostic_group d;
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 2344b5eea00..396a9f6b44e 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -12007,6 +12007,12 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
>       case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
>         return is_nothrow_xible (INIT_EXPR, type1, type2);
>   
> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> +      return reference_from_temporary (type1, type2, /*direct_init=*/true);
> +
> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> +      return reference_from_temporary (type1, type2, /*direct_init=*/false);
> +
>       default:
>         gcc_unreachable ();
>         return false;
> @@ -12088,6 +12094,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
>       case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
>       case CPTK_IS_NOTHROW_ASSIGNABLE:
>       case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
>         if (!check_trait_type (type1)
>   	  || !check_trait_type (type2))
>   	return error_mark_node;
> diff --git a/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> new file mode 100644
> index 00000000000..76de905a35d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> @@ -0,0 +1,214 @@
> +// P2255R2
> +// PR c++/104477
> +// { dg-do compile { target c++11 } }
> +
> +#define SA(X) static_assert((X),#X)
> +
> +struct A { A(); };
> +struct B { operator A(); };
> +struct C { explicit C(); };
> +struct D { explicit operator A(); };
> +struct E { explicit operator A&(); };
> +struct F { explicit operator A&&(); };
> +// Could use a class template with explicit(bool), but then this would need
> +// C++20.
> +struct G {
> +  operator int();
> +  explicit operator int&&();
> +};
> +struct G2 {
> +  explicit operator int();
> +  operator int&&();
> +};
> +struct H {
> +  operator int();
> +  explicit operator int&();
> +};
> +struct H2 {
> +  explicit operator int();
> +  operator int&();
> +};
> +
> +struct Base { };
> +struct Der : Base { };
> +
> +template<typename T, typename RT>
> +struct morph {
> +  mutable T val{};
> +  operator RT() const { return static_cast<RT>(val); }
> +};
> +
> +template<typename T> using id = T;
> +
> +// Built-in types.
> +SA(!__reference_constructs_from_temporary(int, int));
> +SA(!__reference_constructs_from_temporary(int&, void));
> +SA(!__reference_constructs_from_temporary(int&, const void));
> +SA(!__reference_constructs_from_temporary(int&, volatile void));
> +SA(!__reference_constructs_from_temporary(int&, const volatile void));
> +SA(!__reference_constructs_from_temporary(void, void));
> +SA(!__reference_constructs_from_temporary(void, int));
> +SA(!__reference_constructs_from_temporary(int&, int));
> +SA(!__reference_constructs_from_temporary(int&, int&));
> +SA(!__reference_constructs_from_temporary(int&, int&&));
> +SA(!__reference_constructs_from_temporary(int&, long));
> +// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
> +SA(!__reference_constructs_from_temporary(int&, long&));
> +SA(!__reference_constructs_from_temporary(int&, long&&));
> +SA( __reference_constructs_from_temporary(const int&, int));
> +SA(!__reference_constructs_from_temporary(const int&, int&));
> +SA(!__reference_constructs_from_temporary(const int&, const int&));
> +SA(!__reference_constructs_from_temporary(const int&, int&&));
> +SA( __reference_constructs_from_temporary(const int&, long));
> +SA( __reference_constructs_from_temporary(const int&, long&));
> +SA( __reference_constructs_from_temporary(const int&, long&&));
> +SA( __reference_constructs_from_temporary(int&&, int));
> +SA(!__reference_constructs_from_temporary(int&&, int&));
> +SA(!__reference_constructs_from_temporary(int&&, int&&));
> +SA( __reference_constructs_from_temporary(int&&, long));
> +SA( __reference_constructs_from_temporary(int&&, long&));
> +SA( __reference_constructs_from_temporary(int&&, long&&));
> +SA(!__reference_constructs_from_temporary(unsigned int&, double));
> +SA(!__reference_constructs_from_temporary(volatile int&, int));
> +SA(!__reference_constructs_from_temporary(const volatile int&, int));
> +SA(!__reference_constructs_from_temporary(volatile int&, int&));
> +SA(!__reference_constructs_from_temporary(const volatile int&, int&));
> +SA(!__reference_constructs_from_temporary(volatile int&, int&&));
> +SA(!__reference_constructs_from_temporary(const volatile int&, int&&));
> +
> +// Classes.
> +SA(!__reference_constructs_from_temporary(A, A));
> +// A& r(A{}); doesn't construct.
> +SA(!__reference_constructs_from_temporary(A&, A));
> +SA(!__reference_constructs_from_temporary(A&, A&));
> +SA(!__reference_constructs_from_temporary(A&, A&&));
> +// Here we get const struct A & r = (const struct A &) &D.2414;
> +SA( __reference_constructs_from_temporary(const A&, A));
> +SA(!__reference_constructs_from_temporary(const A&, A&));
> +SA(!__reference_constructs_from_temporary(const A&, const A&));
> +SA(!__reference_constructs_from_temporary(const A&, A&&));
> +// Here we get struct A & r = (struct A &) &D.2439;
> +SA( __reference_constructs_from_temporary(A&&, A));
> +SA(!__reference_constructs_from_temporary(A&&, A&));
> +SA(!__reference_constructs_from_temporary(A&&, const A&));
> +SA(!__reference_constructs_from_temporary(A&&, A&&));
> +
> +SA(!__reference_constructs_from_temporary(A, B));
> +SA(!__reference_constructs_from_temporary(A&, B));
> +SA(!__reference_constructs_from_temporary(A&, B&));
> +SA(!__reference_constructs_from_temporary(A&, const B&));
> +SA(!__reference_constructs_from_temporary(A&, B&&));
> +SA( __reference_constructs_from_temporary(const A&, B));
> +SA( __reference_constructs_from_temporary(const A&, B&));
> +// Doesn't construct, so it's false.
> +SA(!__reference_constructs_from_temporary(const A&, const B&));
> +SA( __reference_constructs_from_temporary(const A&, B&&));
> +SA( __reference_constructs_from_temporary(A&&, B));
> +SA( __reference_constructs_from_temporary(A&&, B&));
> +SA(!__reference_constructs_from_temporary(A&&, const B&));
> +SA( __reference_constructs_from_temporary(A&&, B&&));
> +
> +SA(!__reference_constructs_from_temporary(const A&, C));
> +SA(!__reference_constructs_from_temporary(const A&, C&));
> +
> +// Doesn't construct, so it's false.
> +SA(!__reference_constructs_from_temporary(int&, morph<int, int>));
> +// Here we get
> +//   const int & r2 = D.2580 = morph<int, int>::operator int
> +//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
> +SA( __reference_constructs_from_temporary(const int&, morph<int, int>));
> +SA(!__reference_constructs_from_temporary(int&, morph<int, int&>));
> +SA(!__reference_constructs_from_temporary(int&, morph<int, const int&>));
> +SA(!__reference_constructs_from_temporary(int&, morph<int, int&&>));
> +SA( __reference_constructs_from_temporary(const int&, morph<long, long&>));
> +
> +// These are like const C& c(cref); so the explicit ctor C isn't a problem
> +// even in copy-init context.  const C& r = {}; would be a different story.
> +SA(!__reference_constructs_from_temporary(C, C));
> +SA(!__reference_constructs_from_temporary(C&, C));
> +SA(!__reference_constructs_from_temporary(C&, C&));
> +SA(!__reference_constructs_from_temporary(C&, C&&));
> +SA( __reference_constructs_from_temporary(const C&, C));
> +SA(!__reference_constructs_from_temporary(const C&, C&));
> +SA(!__reference_constructs_from_temporary(const C&, const C&));
> +SA(!__reference_constructs_from_temporary(const C&, C&&));
> +SA( __reference_constructs_from_temporary(C&&, C));
> +SA(!__reference_constructs_from_temporary(C&&, C&));
> +SA(!__reference_constructs_from_temporary(C&&, const C&));
> +SA(!__reference_constructs_from_temporary(C&&, C&&));
> +
> +// These are all false ultimately because of CWG 2267, which we implement.
> +SA(!__reference_constructs_from_temporary(A, D));
> +SA(!__reference_constructs_from_temporary(A&, D));
> +SA(!__reference_constructs_from_temporary(A&, D&));
> +SA(!__reference_constructs_from_temporary(A&, const D&));
> +SA(!__reference_constructs_from_temporary(A&, D&&));
> +SA(!__reference_constructs_from_temporary(const A&, D));
> +SA(!__reference_constructs_from_temporary(const A&, D&));
> +SA(!__reference_constructs_from_temporary(const A&, const D&));
> +SA(!__reference_constructs_from_temporary(const A&, D&&));
> +SA(!__reference_constructs_from_temporary(A&&, D));
> +SA(!__reference_constructs_from_temporary(A&&, D&));
> +SA(!__reference_constructs_from_temporary(A&&, const D&));
> +SA(!__reference_constructs_from_temporary(A&&, D&&));
> +
> +SA(!__reference_constructs_from_temporary(A, E));
> +/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
> +   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
> +   so we're not binding the reference to a temporary, although there is
> +   a temporary involved.  So the result is false in both copy- and direct-
> +   init, albeit for different reasons!  */
> +SA(!__reference_constructs_from_temporary(A&, E));
> +// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
> +SA(!__reference_constructs_from_temporary(A&, E&));
> +SA(!__reference_constructs_from_temporary(A&, const E&));
> +SA(!__reference_constructs_from_temporary(A&, E&&));
> +// direct-init:
> +// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
> +SA(!__reference_constructs_from_temporary(const A&, E));
> +SA(!__reference_constructs_from_temporary(const A&, E&));
> +SA(!__reference_constructs_from_temporary(const A&, const E&));
> +SA(!__reference_constructs_from_temporary(const A&, E&&));
> +SA(!__reference_constructs_from_temporary(A&&, E));
> +SA(!__reference_constructs_from_temporary(A&&, E&));
> +SA(!__reference_constructs_from_temporary(A&&, const E&));
> +SA(!__reference_constructs_from_temporary(A&&, E&&));
> +
> +SA(!__reference_constructs_from_temporary(A, F));
> +// A& a1(F{}); and A& a2 = F{}; both invalid.
> +SA(!__reference_constructs_from_temporary(A&, F));
> +SA(!__reference_constructs_from_temporary(A&, F&));
> +SA(!__reference_constructs_from_temporary(A&, const F&));
> +SA(!__reference_constructs_from_temporary(A&, F&&));
> +SA(!__reference_constructs_from_temporary(const A&, F));
> +SA(!__reference_constructs_from_temporary(const A&, F&));
> +SA(!__reference_constructs_from_temporary(const A&, const F&));
> +SA(!__reference_constructs_from_temporary(const A&, F&&));
> +SA(!__reference_constructs_from_temporary(A&&, F));
> +SA(!__reference_constructs_from_temporary(A&&, F&));
> +SA(!__reference_constructs_from_temporary(A&&, const F&));
> +SA(!__reference_constructs_from_temporary(A&&, F&&));
> +
> +/* This is where _converts_ and _constructs_ will differ:
> +   in direct-init we use G::operator int&& (no temporary),
> +   but in copy-init we use G::operator int, where a temporary is created
> +   to be bound to int&&.  */
> +SA(!__reference_constructs_from_temporary(int&&, G));
> +// Similar to the previous one.
> +SA(!__reference_constructs_from_temporary(const int&, H));
> +/* And here I've switched the explicit-ness.  In both copy- and direct-init
> +   we call operator int&, so no temporary.  */
> +SA(!__reference_constructs_from_temporary(int&&, G2));
> +SA(!__reference_constructs_from_temporary(const int&, H2));
> +
> +SA(!__reference_constructs_from_temporary(const Base&, Der));
> +
> +// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
> +SA(!__reference_constructs_from_temporary(int&&, id<int[3]>));
> +
> +// Arrays.
> +SA(!__reference_constructs_from_temporary(int, int[]));
> +SA(!__reference_constructs_from_temporary(int[], int[]));
> +SA(!__reference_constructs_from_temporary(int&, int[]));
> +SA(!__reference_constructs_from_temporary(int&&, int[]));
> +SA(!__reference_constructs_from_temporary(const int&, int[]));
> diff --git a/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> new file mode 100644
> index 00000000000..90196c38742
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> @@ -0,0 +1,214 @@
> +// P2255R2
> +// PR c++/104477
> +// { dg-do compile { target c++11 } }
> +
> +#define SA(X) static_assert((X),#X)
> +
> +struct A { A(); };
> +struct B { operator A(); };
> +struct C { explicit C(); };
> +struct D { explicit operator A(); };
> +struct E { explicit operator A&(); };
> +struct F { explicit operator A&&(); };
> +// Could use a class template with explicit(bool), but then this would need
> +// C++20.
> +struct G {
> +  operator int();
> +  explicit operator int&&();
> +};
> +struct G2 {
> +  explicit operator int();
> +  operator int&&();
> +};
> +struct H {
> +  operator int();
> +  explicit operator int&();
> +};
> +struct H2 {
> +  explicit operator int();
> +  operator int&();
> +};
> +
> +struct Base { };
> +struct Der : Base { };
> +
> +template<typename T, typename RT>
> +struct morph {
> +  mutable T val{};
> +  operator RT() const { return static_cast<RT>(val); }
> +};
> +
> +template<typename T> using id = T;
> +
> +// Built-in types.
> +SA(!__reference_converts_from_temporary(int, int));
> +SA(!__reference_converts_from_temporary(int&, void));
> +SA(!__reference_converts_from_temporary(int&, const void));
> +SA(!__reference_converts_from_temporary(int&, volatile void));
> +SA(!__reference_converts_from_temporary(int&, const volatile void));
> +SA(!__reference_converts_from_temporary(void, void));
> +SA(!__reference_converts_from_temporary(void, int));
> +SA(!__reference_converts_from_temporary(int&, int));
> +SA(!__reference_converts_from_temporary(int&, int&));
> +SA(!__reference_converts_from_temporary(int&, int&&));
> +SA(!__reference_converts_from_temporary(int&, long));
> +// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
> +SA(!__reference_converts_from_temporary(int&, long&));
> +SA(!__reference_converts_from_temporary(int&, long&&));
> +SA( __reference_converts_from_temporary(const int&, int));
> +SA(!__reference_converts_from_temporary(const int&, int&));
> +SA(!__reference_converts_from_temporary(const int&, const int&));
> +SA(!__reference_converts_from_temporary(const int&, int&&));
> +SA( __reference_converts_from_temporary(const int&, long));
> +SA( __reference_converts_from_temporary(const int&, long&));
> +SA( __reference_converts_from_temporary(const int&, long&&));
> +SA( __reference_converts_from_temporary(int&&, int));
> +SA(!__reference_converts_from_temporary(int&&, int&));
> +SA(!__reference_converts_from_temporary(int&&, int&&));
> +SA( __reference_converts_from_temporary(int&&, long));
> +SA( __reference_converts_from_temporary(int&&, long&));
> +SA( __reference_converts_from_temporary(int&&, long&&));
> +SA(!__reference_converts_from_temporary(unsigned int&, double));
> +SA(!__reference_converts_from_temporary(volatile int&, int));
> +SA(!__reference_converts_from_temporary(const volatile int&, int));
> +SA(!__reference_converts_from_temporary(volatile int&, int&));
> +SA(!__reference_converts_from_temporary(const volatile int&, int&));
> +SA(!__reference_converts_from_temporary(volatile int&, int&&));
> +SA(!__reference_converts_from_temporary(const volatile int&, int&&));
> +
> +// Classes.
> +SA(!__reference_converts_from_temporary(A, A));
> +// A& r(A{}); doesn't construct.
> +SA(!__reference_converts_from_temporary(A&, A));
> +SA(!__reference_converts_from_temporary(A&, A&));
> +SA(!__reference_converts_from_temporary(A&, A&&));
> +// Here we get const struct A & r = (const struct A &) &D.2414;
> +SA( __reference_converts_from_temporary(const A&, A));
> +SA(!__reference_converts_from_temporary(const A&, A&));
> +SA(!__reference_converts_from_temporary(const A&, const A&));
> +SA(!__reference_converts_from_temporary(const A&, A&&));
> +// Here we get struct A & r = (struct A &) &D.2439;
> +SA( __reference_converts_from_temporary(A&&, A));
> +SA(!__reference_converts_from_temporary(A&&, A&));
> +SA(!__reference_converts_from_temporary(A&&, const A&));
> +SA(!__reference_converts_from_temporary(A&&, A&&));
> +
> +SA(!__reference_converts_from_temporary(A, B));
> +SA(!__reference_converts_from_temporary(A&, B));
> +SA(!__reference_converts_from_temporary(A&, B&));
> +SA(!__reference_converts_from_temporary(A&, const B&));
> +SA(!__reference_converts_from_temporary(A&, B&&));
> +SA( __reference_converts_from_temporary(const A&, B));
> +SA( __reference_converts_from_temporary(const A&, B&));
> +// Doesn't construct, so it's false.
> +SA(!__reference_converts_from_temporary(const A&, const B&));
> +SA( __reference_converts_from_temporary(const A&, B&&));
> +SA( __reference_converts_from_temporary(A&&, B));
> +SA( __reference_converts_from_temporary(A&&, B&));
> +SA(!__reference_converts_from_temporary(A&&, const B&));
> +SA( __reference_converts_from_temporary(A&&, B&&));
> +
> +SA(!__reference_converts_from_temporary(const A&, C));
> +SA(!__reference_converts_from_temporary(const A&, C&));
> +
> +// Doesn't construct, so it's false.
> +SA(!__reference_converts_from_temporary(int&, morph<int, int>));
> +// Here we get
> +//   const int & r2 = D.2580 = morph<int, int>::operator int
> +//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
> +SA( __reference_converts_from_temporary(const int&, morph<int, int>));
> +SA(!__reference_converts_from_temporary(int&, morph<int, int&>));
> +SA(!__reference_converts_from_temporary(int&, morph<int, const int&>));
> +SA(!__reference_converts_from_temporary(int&, morph<int, int&&>));
> +SA( __reference_converts_from_temporary(const int&, morph<long, long&>));
> +
> +// These are like const C& c(cref); so the explicit ctor C isn't a problem
> +// even in copy-init context.  const C& r = {}; would be a different story.
> +SA(!__reference_converts_from_temporary(C, C));
> +SA(!__reference_converts_from_temporary(C&, C));
> +SA(!__reference_converts_from_temporary(C&, C&));
> +SA(!__reference_converts_from_temporary(C&, C&&));
> +SA( __reference_converts_from_temporary(const C&, C));
> +SA(!__reference_converts_from_temporary(const C&, C&));
> +SA(!__reference_converts_from_temporary(const C&, const C&));
> +SA(!__reference_converts_from_temporary(const C&, C&&));
> +SA( __reference_converts_from_temporary(C&&, C));
> +SA(!__reference_converts_from_temporary(C&&, C&));
> +SA(!__reference_converts_from_temporary(C&&, const C&));
> +SA(!__reference_converts_from_temporary(C&&, C&&));
> +
> +// These are all false ultimately because of CWG 2267, which we implement.
> +SA(!__reference_converts_from_temporary(A, D));
> +SA(!__reference_converts_from_temporary(A&, D));
> +SA(!__reference_converts_from_temporary(A&, D&));
> +SA(!__reference_converts_from_temporary(A&, const D&));
> +SA(!__reference_converts_from_temporary(A&, D&&));
> +SA(!__reference_converts_from_temporary(const A&, D));
> +SA(!__reference_converts_from_temporary(const A&, D&));
> +SA(!__reference_converts_from_temporary(const A&, const D&));
> +SA(!__reference_converts_from_temporary(const A&, D&&));
> +SA(!__reference_converts_from_temporary(A&&, D));
> +SA(!__reference_converts_from_temporary(A&&, D&));
> +SA(!__reference_converts_from_temporary(A&&, const D&));
> +SA(!__reference_converts_from_temporary(A&&, D&&));
> +
> +SA(!__reference_converts_from_temporary(A, E));
> +/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
> +   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
> +   so we're not binding the reference to a temporary, although there is
> +   a temporary involved.  So the result is false in both copy- and direct-
> +   init, albeit for different reasons!  */
> +SA(!__reference_converts_from_temporary(A&, E));
> +// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
> +SA(!__reference_converts_from_temporary(A&, E&));
> +SA(!__reference_converts_from_temporary(A&, const E&));
> +SA(!__reference_converts_from_temporary(A&, E&&));
> +// direct-init:
> +// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
> +SA(!__reference_converts_from_temporary(const A&, E));
> +SA(!__reference_converts_from_temporary(const A&, E&));
> +SA(!__reference_converts_from_temporary(const A&, const E&));
> +SA(!__reference_converts_from_temporary(const A&, E&&));
> +SA(!__reference_converts_from_temporary(A&&, E));
> +SA(!__reference_converts_from_temporary(A&&, E&));
> +SA(!__reference_converts_from_temporary(A&&, const E&));
> +SA(!__reference_converts_from_temporary(A&&, E&&));
> +
> +SA(!__reference_converts_from_temporary(A, F));
> +// A& a1(F{}); and A& a2 = F{}; both invalid.
> +SA(!__reference_converts_from_temporary(A&, F));
> +SA(!__reference_converts_from_temporary(A&, F&));
> +SA(!__reference_converts_from_temporary(A&, const F&));
> +SA(!__reference_converts_from_temporary(A&, F&&));
> +SA(!__reference_converts_from_temporary(const A&, F));
> +SA(!__reference_converts_from_temporary(const A&, F&));
> +SA(!__reference_converts_from_temporary(const A&, const F&));
> +SA(!__reference_converts_from_temporary(const A&, F&&));
> +SA(!__reference_converts_from_temporary(A&&, F));
> +SA(!__reference_converts_from_temporary(A&&, F&));
> +SA(!__reference_converts_from_temporary(A&&, const F&));
> +SA(!__reference_converts_from_temporary(A&&, F&&));
> +
> +/* This is where _converts_ and _constructs_ will differ:
> +   in direct-init we use G::operator int&& (no temporary),
> +   but in copy-init we use G::operator int, where a temporary is created
> +   to be bound to int&&.  */
> +SA( __reference_converts_from_temporary(int&&, G));
> +// Similar to the previous one.
> +SA( __reference_converts_from_temporary(const int&, H));
> +/* And here I've switched the explicit-ness.  In both copy- and direct-init
> +   we call operator int&, so no temporary.  */
> +SA(!__reference_converts_from_temporary(int&&, G2));
> +SA(!__reference_converts_from_temporary(const int&, H2));
> +
> +SA(!__reference_converts_from_temporary(const Base&, Der));
> +
> +// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
> +SA(!__reference_converts_from_temporary(int&&, id<int[3]>));
> +
> +// Arrays.
> +SA(!__reference_converts_from_temporary(int, int[]));
> +SA(!__reference_converts_from_temporary(int[], int[]));
> +SA(!__reference_converts_from_temporary(int&, int[]));
> +SA(!__reference_converts_from_temporary(int&&, int[]));
> +SA(!__reference_converts_from_temporary(const int&, int[]));
> diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
> index 2572d8edd69..bbeb049e1f9 100644
> --- a/libstdc++-v3/include/std/type_traits
> +++ b/libstdc++-v3/include/std/type_traits
> @@ -3508,6 +3508,45 @@ template<typename _Ret, typename _Fn, typename... _Args>
>     template<typename _Tp>
>       inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
>   
> +#define __cpp_lib_reference_from_temporary 202202L
> +
> +  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
> +  /// direct-initialization, and a temporary object would be bound to
> +  /// the reference, false otherwise.
> +  /// @since C++23
> +  template<typename _Tp, typename _Up>
> +    struct reference_constructs_from_temporary
> +    : public __bool_constant<__reference_constructs_from_temporary(_Tp, _Up)>
> +    {
> +      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
> +		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
> +	"template argument must be a complete class or an unbounded array");
> +    };
> +
> +  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
> +  /// copy-initialization, and a temporary object would be bound to
> +  /// the reference, false otherwise.
> +  /// @since C++23
> +  template<typename _Tp, typename _Up>
> +    struct reference_converts_from_temporary
> +    : public __bool_constant<__reference_converts_from_temporary(_Tp, _Up)>
> +    {
> +      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
> +		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
> +	"template argument must be a complete class or an unbounded array");
> +    };
> +
> +  /// @ingroup variable_templates
> +  /// @since C++23
> +  template<typename _Tp, typename _Up>
> +    inline constexpr bool reference_constructs_from_temporary_v
> +      = reference_constructs_from_temporary<_Tp, _Up>::value;
> +
> +  /// @ingroup variable_templates
> +  /// @since C++23
> +  template<typename _Tp, typename _Up>
> +    inline constexpr bool reference_converts_from_temporary_v
> +      = reference_converts_from_temporary<_Tp, _Up>::value;
>   #endif // C++23
>   
>   #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
> diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
> index 22280e1a349..5edca2f3007 100644
> --- a/libstdc++-v3/include/std/version
> +++ b/libstdc++-v3/include/std/version
> @@ -300,10 +300,11 @@
>   #endif
>   
>   #if __cplusplus > 202002L
> -// c++2b
> +// c++23
>   #define __cpp_lib_byteswap 202110L
>   #define __cpp_lib_constexpr_typeinfo 202106L
>   #define __cpp_lib_is_scoped_enum 202011L
> +#define __cpp_lib_reference_from_temporary 202202L
>   
>   #if _GLIBCXX_HOSTED
>   #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
> @@ -335,7 +336,7 @@
>   #define __cpp_lib_to_underlying 202102L
>   #define __cpp_lib_unreachable 202202L
>   #endif
> -#endif // C++2b
> +#endif // C++23
>   #endif // C++20
>   #endif // C++17
>   #endif // C++14
> diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> new file mode 100644
> index 00000000000..2f62e54d46d
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> @@ -0,0 +1,110 @@
> +// Copyright (C) 2022 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library.  This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, or (at your option)
> +// any later version.
> +
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +// GNU General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3.  If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +// { dg-options "-std=gnu++23" }
> +// { dg-do compile { target c++23 } }
> +
> +#include <type_traits>
> +#include <testsuite_tr1.h>
> +
> +#ifndef __cpp_lib_reference_from_temporary
> +# error "Feature test macro for reference_from_temporary is missing in <version>"
> +#elif __cpp_lib_reference_from_temporary < 202202L
> +# error "Feature test macro for reference_from_temporary has wrong value in <version>"
> +#endif
> +
> +void test01()
> +{
> +  using std::reference_constructs_from_temporary;
> +  using std::reference_converts_from_temporary;
> +  using namespace __gnu_test;
> +
> +  struct A { A(); };
> +
> +  struct B {
> +    operator int();
> +    explicit operator int&&();
> +  };
> +
> +  struct C {
> +    operator int();
> +    explicit operator int&();
> +  };
> +
> +  static_assert(test_property<reference_constructs_from_temporary, int, int>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, void>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, const volatile void>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, void, void>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, int>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, int&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, int&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, long>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, long&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, long&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const int&, int>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const int&, int&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const int&, int&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const int&, long>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const int&, long&>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const int&, long&&>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&, int>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&, int&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&, int&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&, long>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&, long&>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&, long&&>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const A&, A>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const A&, A&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, A&&, A>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&, int[]>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const int&, int[]>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&, int[]>(false), "");
> +
> +  static_assert(test_property<reference_converts_from_temporary, int, int>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, void>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, const volatile void>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, void, void>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, int>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, int&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, int&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, long>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, long&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, long&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, const int&, int>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const int&, int&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, const int&, int&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, const int&, long>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const int&, long&>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const int&, long&&>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&, int>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&, int&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&, int&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&, long>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&, long&>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&, long&&>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const A&, A>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const A&, A&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, A&&, A>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&, int[]>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, const int&, int[]>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&, int[]>(false), "");
> +
> +  static_assert(test_property<reference_constructs_from_temporary, int&&, B>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const int&, C>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&, B>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const int&, C>(true), "");
> +}
> diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> new file mode 100644
> index 00000000000..65770754299
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> @@ -0,0 +1,28 @@
> +// Copyright (C) 2022 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library.  This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, or (at your option)
> +// any later version.
> +
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +// GNU General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3.  If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +// { dg-options "-std=gnu++23" }
> +// { dg-do compile { target c++23 } }
> +
> +#include <type_traits>
> +#include <string>
> +
> +void test01()
> +{
> +  static_assert(std::reference_converts_from_temporary_v<const std::string&, const char*>);
> +  static_assert(std::reference_constructs_from_temporary_v<const std::string&, const char*>);
> +}
> diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> new file mode 100644
> index 00000000000..f56e7c0dabc
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> @@ -0,0 +1,27 @@
> +// Copyright (C) 2022 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library.  This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, or (at your option)
> +// any later version.
> +
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +// GNU General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3.  If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +// { dg-options "-std=gnu++23" }
> +// { dg-do compile { target c++23 } }
> +
> +#include <version>
> +
> +#ifndef __cpp_lib_reference_from_temporary
> +# error "Feature test macro for reference_from_temporary is missing in <version>"
> +#elif __cpp_lib_reference_from_temporary < 202202L
> +# error "Feature test macro for reference_from_temporary has wrong value in <version>"
> +#endif
> diff --git a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
> index 9b3957f7d47..2b03ad7067d 100644
> --- a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
> +++ b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
> @@ -346,3 +346,17 @@ static_assert(disjunction_v<false_type, false_type,
>                 true_type>, "");
>   static_assert(!disjunction_v<false_type, false_type,
>                 false_type>, "");
> +#if __cpp_lib_reference_from_temporary >= 202202L
> +static_assert(std::reference_converts_from_temporary_v<int&&, int>
> +	      && std::reference_converts_from_temporary_v<const int&, int>
> +	      && !std::reference_converts_from_temporary_v<int&&, int&&>
> +	      && !std::reference_converts_from_temporary_v<const int&, int&&>
> +	      && std::reference_converts_from_temporary_v<int&&, long&&>
> +	      && std::reference_converts_from_temporary_v<int&&, long>, "");
> +static_assert(std::reference_constructs_from_temporary_v<int&&, int>
> +	      && std::reference_constructs_from_temporary_v<const int&, int>
> +	      && !std::reference_constructs_from_temporary_v<int&&, int&&>
> +	      && !std::reference_constructs_from_temporary_v<const int&, int&&>
> +	      && std::reference_constructs_from_temporary_v<int&&, long&&>
> +	      && std::reference_constructs_from_temporary_v<int&&, long>, "");
> +#endif
> 
> base-commit: 55bb77b50a5ec520f28978ac3fdac1983200e1f7


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-12 20:10 ` [PATCH] " Jason Merrill
@ 2022-07-12 20:15   ` Jason Merrill
  2022-07-14 17:43     ` Marek Polacek
  2022-07-14 17:41   ` [PATCH v3] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
  1 sibling, 1 reply; 15+ messages in thread
From: Jason Merrill @ 2022-07-12 20:15 UTC (permalink / raw)
  To: Marek Polacek, GCC Patches, libstdc++, Jonathan Wakely

On 7/12/22 16:10, Jason Merrill wrote:
> On 7/8/22 13:41, Marek Polacek wrote:
>> This patch implements C++23 P2255R2, which adds two new type traits to
>> detect reference binding to a temporary.  They can be used to detect code
>> like
>>
>>    std::tuple<const std::string&> t("meow");
>>
>> which is incorrect because it always creates a dangling reference, 
>> because
>> the std::string temporary is created inside the selected constructor of
>> std::tuple, and not outside it.
>>
>> There are two new compiler builtins, 
>> __reference_constructs_from_temporary
>> and __reference_converts_from_temporary.  The former is used to simulate
>> direct- and the latter copy-initialization context.  But I had a hard 
>> time
>> finding a test where there's actually a difference.  Under DR 2267, both
>> of these are invalid:
>>
>>    struct A { } a;
>>    struct B { explicit B(const A&); };
>>    const B &b1{a};
>>    const B &b2(a);
>>
>> so I had to peruse [over.match.ref], and eventually realized that the
>> difference can be seen here:
>>
>>    struct G {
>>      operator int(); // #1
>>      explicit operator int&&(); // #2
>>    };
>>
>> int&& r1(G{}); // use #2 (no temporary)
>> int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)
>>
>> The implementation itself was rather straightforward because we already
>> have conv_binds_ref_to_prvalue.  The main function here is
>> reference_from_temporary.  The renaming to ref_conv_binds_to_temporary_p
>> is because previously the function didn't distinguish between an invalid
>> conversion and one that binds to a prvalue.
>>
>> The patch also adds the relevant class and variable templates to 
>> <type_traits>.
>>
>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>
>>     PR c++/104477
>>
>> gcc/c-family/ChangeLog:
>>
>>     * c-common.cc (c_common_reswords): Add
>>     __reference_constructs_from_temporary and
>>     __reference_converts_from_temporary.
>>     * c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
>>     RID_REF_CONVERTS_FROM_TEMPORARY.
>>
>> gcc/cp/ChangeLog:
>>
>>     * call.cc (ref_conv_binds_directly_p): Rename to ...
>>     (ref_conv_binds_to_temporary_p): ... this.  Add a new bool
>>     parameter.  Return true only if the conversion is valid and
>>     conv_binds_ref_to_prvalue returns true.
>>     * constraint.cc (diagnose_trait_expr): Handle
>>     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and 
>> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>>     * cp-tree.h (enum cp_trait_kind): Add 
>> CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
>>     and CPTK_REF_CONVERTS_FROM_TEMPORARY.
>>     (ref_conv_binds_directly_p): Rename to ...
>>     (ref_conv_binds_to_temporary_p): ... this.
>>     (reference_from_temporary): Declare.
>>     * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
>>     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and 
>> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>>     * method.cc (reference_from_temporary): New.
>>     * parser.cc (cp_parser_primary_expression): Handle
>>     RID_REF_CONSTRUCTS_FROM_TEMPORARY and 
>> RID_REF_CONVERTS_FROM_TEMPORARY.
>>     (cp_parser_trait_expr): Likewise.
>>     (warn_for_range_copy): Adjust to call ref_conv_binds_to_temporary_p.
>>     * semantics.cc (trait_expr_value): Handle
>>     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and 
>> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>>     (finish_trait_expr): Likewise.
>>
>> libstdc++-v3/ChangeLog:
>>
>>     * include/std/type_traits (reference_constructs_from_temporary,
>>     reference_converts_from_temporary): New class templates.
>>     (reference_constructs_from_temporary_v,
>>     reference_converts_from_temporary_v): New variable templates.
>>     (__cpp_lib_reference_from_temporary): Define for C++23.
>>     * include/std/version (__cpp_lib_reference_from_temporary): Define 
>> for
>>     C++23.
>>     * testsuite/20_util/variable_templates_for_traits.cc: Test
>>     reference_constructs_from_temporary_v and
>>     reference_converts_from_temporary_v.
>>     * testsuite/20_util/reference_from_temporary/value.cc: New test.
>>     * testsuite/20_util/reference_from_temporary/value2.cc: New test.
>>     * testsuite/20_util/reference_from_temporary/version.cc: New test.
>>
>> gcc/testsuite/ChangeLog:
>>
>>     * g++.dg/ext/reference_constructs_from_temporary1.C: New test.
>>     * g++.dg/ext/reference_converts_from_temporary1.C: New test.
>> ---
>>   gcc/c-family/c-common.cc                      |   4 +
>>   gcc/c-family/c-common.h                       |   2 +
>>   gcc/cp/call.cc                                |  14 +-
>>   gcc/cp/constraint.cc                          |   8 +
>>   gcc/cp/cp-tree.h                              |   7 +-
>>   gcc/cp/cxx-pretty-print.cc                    |   6 +
>>   gcc/cp/method.cc                              |  28 +++
>>   gcc/cp/parser.cc                              |  14 +-
>>   gcc/cp/semantics.cc                           |   8 +
>>   .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
>>   .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
>>   libstdc++-v3/include/std/type_traits          |  39 ++++
>>   libstdc++-v3/include/std/version              |   5 +-
>>   .../20_util/reference_from_temporary/value.cc | 110 +++++++++
>>   .../reference_from_temporary/value2.cc        |  28 +++
>>   .../reference_from_temporary/version.cc       |  27 +++
>>   .../20_util/variable_templates_for_traits.cc  |  14 ++
>>   17 files changed, 730 insertions(+), 12 deletions(-)
>>   create mode 100644 
>> gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
>>   create mode 100644 
>> gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
>>   create mode 100644 
>> libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
>>   create mode 100644 
>> libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
>>   create mode 100644 
>> libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
>>
>> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
>> index 1b8e73f7bc5..655c3aefee6 100644
>> --- a/gcc/c-family/c-common.cc
>> +++ b/gcc/c-family/c-common.cc
>> @@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
>>     { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
>>     { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
>>     { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, 
>> D_CXXONLY },
>> +  { "__reference_constructs_from_temporary", 
>> RID_REF_CONSTRUCTS_FROM_TEMPORARY,
>> +                    D_CXXONLY },
>> +  { "__reference_converts_from_temporary", 
>> RID_REF_CONVERTS_FROM_TEMPORARY,
>> +                    D_CXXONLY },
>>     /* C++ transactional memory.  */
>>     { "synchronized",    RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
>> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
>> index c0900848965..f9064393b4e 100644
>> --- a/gcc/c-family/c-common.h
>> +++ b/gcc/c-family/c-common.h
>> @@ -184,6 +184,8 @@ enum rid
>>     RID_IS_UNION,                RID_UNDERLYING_TYPE,
>>     RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
>>     RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
>> +  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
>> +  RID_REF_CONVERTS_FROM_TEMPORARY,
>>     /* C++11 */
>>     RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, 
>> RID_STATIC_ASSERT,
>> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
>> index fc98552fda2..1ba209f61f1 100644
>> --- a/gcc/cp/call.cc
>> +++ b/gcc/cp/call.cc
>> @@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
>>     return conv_is_prvalue (next_conversion (c));
>>   }
>> -/* True iff converting EXPR to a reference type TYPE does not involve
>> -   creating a temporary.  */
>> +/* True iff converting EXPR to a reference type TYPE binds the 
>> reference to
>> +   a temporary.  DIRECT_INIT_P says whether the conversion should be 
>> done
>> +   in direct- or copy-initialization context.  */
>>   bool
>> -ref_conv_binds_directly_p (tree type, tree expr)
>> +ref_conv_binds_to_temporary_p (tree type, tree expr,
>> +                   bool direct_init_p /*= false*/)
>>   {
>>     gcc_assert (TYPE_REF_P (type));
>>     /* Get the high-water mark for the CONVERSION_OBSTACK.  */
>>     void *p = conversion_obstack_alloc (0);
>> +  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
>>     conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
>> -                      /*c_cast_p=*/false,
>> -                      LOOKUP_IMPLICIT, tf_none);
>> -  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
>> +                      /*c_cast_p=*/false, flags, tf_none);
>> +  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue (conv);
>>     /* Free all the conversions we allocated.  */
>>     obstack_free (&conversion_obstack, p);
>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>> index 591155cee22..648cc9d176d 100644
>> --- a/gcc/cp/constraint.cc
>> +++ b/gcc/cp/constraint.cc
>> @@ -3687,6 +3687,14 @@ diagnose_trait_expr (tree expr, tree args)
>>       case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
>>         inform (loc, "  %qT does not have unique object 
>> representations", t1);
>>         break;
>> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
>> +      inform (loc, "  %qT is not a reference that binds to a temporary "
>> +          "object of type %qT (direct-initialization)", t1, t2);
>> +      break;
>> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
>> +      inform (loc, "  %qT is not a reference that binds to a temporary "
>> +          "object of type %qT (copy-initialization)", t1, t2);
>> +      break;
>>       case CPTK_BASES:
>>       case CPTK_DIRECT_BASES:
>>       case CPTK_UNDERLYING_TYPE:
>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>> index 2fde4f83b41..c3bed31e455 100644
>> --- a/gcc/cp/cp-tree.h
>> +++ b/gcc/cp/cp-tree.h
>> @@ -1397,7 +1397,9 @@ enum cp_trait_kind
>>     CPTK_IS_ASSIGNABLE,
>>     CPTK_IS_CONSTRUCTIBLE,
>>     CPTK_IS_NOTHROW_ASSIGNABLE,
>> -  CPTK_IS_NOTHROW_CONSTRUCTIBLE
>> +  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
>> +  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
>> +  CPTK_REF_CONVERTS_FROM_TEMPORARY
>>   };
>>   /* The types that we are processing.  */
>> @@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p            
>> (const_tree);
>>   extern tree type_decays_to            (tree);
>>   extern tree extract_call_expr            (tree);
>>   extern tree build_trivial_dtor_call        (tree, bool = false);
>> -extern bool ref_conv_binds_directly_p        (tree, tree);
>> +extern bool ref_conv_binds_to_temporary_p    (tree, tree, bool = false);
>>   extern tree build_user_type_conversion        (tree, tree, int,
>>                            tsubst_flags_t);
>>   extern tree build_new_function_call        (tree, vec<tree, va_gc> **,
>> @@ -7105,6 +7107,7 @@ extern tree forward_parm            (tree);
>>   extern bool is_trivially_xible            (enum tree_code, tree, tree);
>>   extern bool is_nothrow_xible            (enum tree_code, tree, tree);
>>   extern bool is_xible                (enum tree_code, tree, tree);
>> +extern bool reference_from_temporary        (tree, tree, bool);
>>   extern tree get_defaulted_eh_spec        (tree, tsubst_flags_t = 
>> tf_warning_or_error);
>>   extern bool maybe_explain_implicit_delete    (tree);
>>   extern void explain_implicit_non_constexpr    (tree);
>> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
>> index 7e4db2e413b..44590830a61 100644
>> --- a/gcc/cp/cxx-pretty-print.cc
>> +++ b/gcc/cp/cxx-pretty-print.cc
>> @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer 
>> *pp, tree t)
>>       case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
>>         pp_cxx_ws_string (pp, "__is_nothrow_constructible");
>>         break;
>> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
>> +      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
>> +      break;
>> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
>> +      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
>> +      break;
>>       default:
>>         gcc_unreachable ();
>> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
>> index 0dffd648b0b..dd9715b6725 100644
>> --- a/gcc/cp/method.cc
>> +++ b/gcc/cp/method.cc
>> @@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to, tree from)
>>     return !!expr;
>>   }
>> +/* Return true iff conjunction_v<is_reference<T>, is_constructible<T, 
>> U>> is
>> +   true, and the initialization
>> +     T t(VAL<U>); // DIRECT_INIT_P
>> +   or
>> +     T t = VAL<U>; // !DIRECT_INIT_P
>> +   binds t to a temporary object whose lifetime is extended.
>> +   VAL<T> is defined in [meta.unary.prop]:
>> +   -- If T is a reference or function type, VAL<T> is an expression 
>> with the
>> +   same type and value category as declval<T>().
>> +   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
>> +
>> +bool
>> +reference_from_temporary (tree to, tree from, bool direct_init_p)
>> +{
>> +  /* Check is_reference<T>.  */
>> +  if (!TYPE_REF_P (to))
>> +    return false;
>> +  /* Check is_constructible<T, U>.
>> +     ??? This check doesn't seem to be necessary; if T isn't 
>> constructible
>> +     from U, we won't be able to create a conversion.  */
>> +  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
>> +    return false;
> 
> I agree with the comment, did you try leaving this out?  If it stays I'd 
> think it needs to consider direct_init_p.
> 
>> +  tree val = build_stub_object (from);
>> +  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
>> +    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) : rvalue 
>> (val);
>> +  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
>> +}
>> +
>>   /* Categorize various special_function_kinds.  */
>>   #define SFK_CTOR_P(sfk) \
>>     ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
>> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
>> index bf9ea3685f8..edee94bda13 100644
>> --- a/gcc/cp/parser.cc
>> +++ b/gcc/cp/parser.cc
>> @@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
>>       case RID_IS_CONSTRUCTIBLE:
>>       case RID_IS_NOTHROW_ASSIGNABLE:
>>       case RID_IS_NOTHROW_CONSTRUCTIBLE:
>> +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
>> +    case RID_REF_CONVERTS_FROM_TEMPORARY:
>>         return cp_parser_trait_expr (parser, token->keyword);
>>       // C++ concepts
>> @@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser, enum 
>> rid keyword)
>>         kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
>>         variadic = true;
>>         break;
>> +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
>> +      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
>> +      binary = true;
>> +      break;
>> +    case RID_REF_CONVERTS_FROM_TEMPORARY:
>> +      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
>> +      binary = true;
>> +      break;
>>       default:
>>         gcc_unreachable ();
>>       }
>> @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
>>     if (TYPE_REF_P (type))
>>       {
>> -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
>> +      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type, 
>> expr))
>>       {
>>         auto_diagnostic_group d;
>>         if (warning_at (loc, OPT_Wrange_loop_construct,
>> @@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
>>     tree rtype = cp_build_reference_type (type, /*rval*/false);
>>     /* If we could initialize the reference directly, it wouldn't 
>> involve any
>>        copies.  */
>> -  if (!ref_conv_binds_directly_p (rtype, expr))
>> +  if (ref_conv_binds_to_temporary_p (rtype, expr))
>>       return;
> 
> I think this case wants the old handling of invalid conversions you 
> mentioned in your intro; we don't want to suggest changing to a 
> reference if that's ill-formed.
> 
> In passing we might change the comment to "If we can initialize a 
> reference directly, suggest that to avoid the copy." and move it above 
> the rtype declaration.

Hmm, and I suspect we get false positives when expr is a prvalue, so 
initializing a non-reference variable of the same cv-unqualified type 
involves no extra copy?

>>     auto_diagnostic_group d;
>> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
>> index 2344b5eea00..396a9f6b44e 100644
>> --- a/gcc/cp/semantics.cc
>> +++ b/gcc/cp/semantics.cc
>> @@ -12007,6 +12007,12 @@ trait_expr_value (cp_trait_kind kind, tree 
>> type1, tree type2)
>>       case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
>>         return is_nothrow_xible (INIT_EXPR, type1, type2);
>> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
>> +      return reference_from_temporary (type1, type2, 
>> /*direct_init=*/true);
>> +
>> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
>> +      return reference_from_temporary (type1, type2, 
>> /*direct_init=*/false);
>> +
>>       default:
>>         gcc_unreachable ();
>>         return false;
>> @@ -12088,6 +12094,8 @@ finish_trait_expr (location_t loc, 
>> cp_trait_kind kind, tree type1, tree type2)
>>       case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
>>       case CPTK_IS_NOTHROW_ASSIGNABLE:
>>       case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
>> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
>> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
>>         if (!check_trait_type (type1)
>>         || !check_trait_type (type2))
>>       return error_mark_node;
>> diff --git 
>> a/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C 
>> b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
>> new file mode 100644
>> index 00000000000..76de905a35d
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
>> @@ -0,0 +1,214 @@
>> +// P2255R2
>> +// PR c++/104477
>> +// { dg-do compile { target c++11 } }
>> +
>> +#define SA(X) static_assert((X),#X)
>> +
>> +struct A { A(); };
>> +struct B { operator A(); };
>> +struct C { explicit C(); };
>> +struct D { explicit operator A(); };
>> +struct E { explicit operator A&(); };
>> +struct F { explicit operator A&&(); };
>> +// Could use a class template with explicit(bool), but then this 
>> would need
>> +// C++20.
>> +struct G {
>> +  operator int();
>> +  explicit operator int&&();
>> +};
>> +struct G2 {
>> +  explicit operator int();
>> +  operator int&&();
>> +};
>> +struct H {
>> +  operator int();
>> +  explicit operator int&();
>> +};
>> +struct H2 {
>> +  explicit operator int();
>> +  operator int&();
>> +};
>> +
>> +struct Base { };
>> +struct Der : Base { };
>> +
>> +template<typename T, typename RT>
>> +struct morph {
>> +  mutable T val{};
>> +  operator RT() const { return static_cast<RT>(val); }
>> +};
>> +
>> +template<typename T> using id = T;
>> +
>> +// Built-in types.
>> +SA(!__reference_constructs_from_temporary(int, int));
>> +SA(!__reference_constructs_from_temporary(int&, void));
>> +SA(!__reference_constructs_from_temporary(int&, const void));
>> +SA(!__reference_constructs_from_temporary(int&, volatile void));
>> +SA(!__reference_constructs_from_temporary(int&, const volatile void));
>> +SA(!__reference_constructs_from_temporary(void, void));
>> +SA(!__reference_constructs_from_temporary(void, int));
>> +SA(!__reference_constructs_from_temporary(int&, int));
>> +SA(!__reference_constructs_from_temporary(int&, int&));
>> +SA(!__reference_constructs_from_temporary(int&, int&&));
>> +SA(!__reference_constructs_from_temporary(int&, long));
>> +// non-const lvalue reference to type 'int' cannot bind to a value of 
>> unrelated type 'long'
>> +SA(!__reference_constructs_from_temporary(int&, long&));
>> +SA(!__reference_constructs_from_temporary(int&, long&&));
>> +SA( __reference_constructs_from_temporary(const int&, int));
>> +SA(!__reference_constructs_from_temporary(const int&, int&));
>> +SA(!__reference_constructs_from_temporary(const int&, const int&));
>> +SA(!__reference_constructs_from_temporary(const int&, int&&));
>> +SA( __reference_constructs_from_temporary(const int&, long));
>> +SA( __reference_constructs_from_temporary(const int&, long&));
>> +SA( __reference_constructs_from_temporary(const int&, long&&));
>> +SA( __reference_constructs_from_temporary(int&&, int));
>> +SA(!__reference_constructs_from_temporary(int&&, int&));
>> +SA(!__reference_constructs_from_temporary(int&&, int&&));
>> +SA( __reference_constructs_from_temporary(int&&, long));
>> +SA( __reference_constructs_from_temporary(int&&, long&));
>> +SA( __reference_constructs_from_temporary(int&&, long&&));
>> +SA(!__reference_constructs_from_temporary(unsigned int&, double));
>> +SA(!__reference_constructs_from_temporary(volatile int&, int));
>> +SA(!__reference_constructs_from_temporary(const volatile int&, int));
>> +SA(!__reference_constructs_from_temporary(volatile int&, int&));
>> +SA(!__reference_constructs_from_temporary(const volatile int&, int&));
>> +SA(!__reference_constructs_from_temporary(volatile int&, int&&));
>> +SA(!__reference_constructs_from_temporary(const volatile int&, int&&));
>> +
>> +// Classes.
>> +SA(!__reference_constructs_from_temporary(A, A));
>> +// A& r(A{}); doesn't construct.
>> +SA(!__reference_constructs_from_temporary(A&, A));
>> +SA(!__reference_constructs_from_temporary(A&, A&));
>> +SA(!__reference_constructs_from_temporary(A&, A&&));
>> +// Here we get const struct A & r = (const struct A &) &D.2414;
>> +SA( __reference_constructs_from_temporary(const A&, A));
>> +SA(!__reference_constructs_from_temporary(const A&, A&));
>> +SA(!__reference_constructs_from_temporary(const A&, const A&));
>> +SA(!__reference_constructs_from_temporary(const A&, A&&));
>> +// Here we get struct A & r = (struct A &) &D.2439;
>> +SA( __reference_constructs_from_temporary(A&&, A));
>> +SA(!__reference_constructs_from_temporary(A&&, A&));
>> +SA(!__reference_constructs_from_temporary(A&&, const A&));
>> +SA(!__reference_constructs_from_temporary(A&&, A&&));
>> +
>> +SA(!__reference_constructs_from_temporary(A, B));
>> +SA(!__reference_constructs_from_temporary(A&, B));
>> +SA(!__reference_constructs_from_temporary(A&, B&));
>> +SA(!__reference_constructs_from_temporary(A&, const B&));
>> +SA(!__reference_constructs_from_temporary(A&, B&&));
>> +SA( __reference_constructs_from_temporary(const A&, B));
>> +SA( __reference_constructs_from_temporary(const A&, B&));
>> +// Doesn't construct, so it's false.
>> +SA(!__reference_constructs_from_temporary(const A&, const B&));
>> +SA( __reference_constructs_from_temporary(const A&, B&&));
>> +SA( __reference_constructs_from_temporary(A&&, B));
>> +SA( __reference_constructs_from_temporary(A&&, B&));
>> +SA(!__reference_constructs_from_temporary(A&&, const B&));
>> +SA( __reference_constructs_from_temporary(A&&, B&&));
>> +
>> +SA(!__reference_constructs_from_temporary(const A&, C));
>> +SA(!__reference_constructs_from_temporary(const A&, C&));
>> +
>> +// Doesn't construct, so it's false.
>> +SA(!__reference_constructs_from_temporary(int&, morph<int, int>));
>> +// Here we get
>> +//   const int & r2 = D.2580 = morph<int, int>::operator int
>> +//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
>> +SA( __reference_constructs_from_temporary(const int&, morph<int, int>));
>> +SA(!__reference_constructs_from_temporary(int&, morph<int, int&>));
>> +SA(!__reference_constructs_from_temporary(int&, morph<int, const 
>> int&>));
>> +SA(!__reference_constructs_from_temporary(int&, morph<int, int&&>));
>> +SA( __reference_constructs_from_temporary(const int&, morph<long, 
>> long&>));
>> +
>> +// These are like const C& c(cref); so the explicit ctor C isn't a 
>> problem
>> +// even in copy-init context.  const C& r = {}; would be a different 
>> story.
>> +SA(!__reference_constructs_from_temporary(C, C));
>> +SA(!__reference_constructs_from_temporary(C&, C));
>> +SA(!__reference_constructs_from_temporary(C&, C&));
>> +SA(!__reference_constructs_from_temporary(C&, C&&));
>> +SA( __reference_constructs_from_temporary(const C&, C));
>> +SA(!__reference_constructs_from_temporary(const C&, C&));
>> +SA(!__reference_constructs_from_temporary(const C&, const C&));
>> +SA(!__reference_constructs_from_temporary(const C&, C&&));
>> +SA( __reference_constructs_from_temporary(C&&, C));
>> +SA(!__reference_constructs_from_temporary(C&&, C&));
>> +SA(!__reference_constructs_from_temporary(C&&, const C&));
>> +SA(!__reference_constructs_from_temporary(C&&, C&&));
>> +
>> +// These are all false ultimately because of CWG 2267, which we 
>> implement.
>> +SA(!__reference_constructs_from_temporary(A, D));
>> +SA(!__reference_constructs_from_temporary(A&, D));
>> +SA(!__reference_constructs_from_temporary(A&, D&));
>> +SA(!__reference_constructs_from_temporary(A&, const D&));
>> +SA(!__reference_constructs_from_temporary(A&, D&&));
>> +SA(!__reference_constructs_from_temporary(const A&, D));
>> +SA(!__reference_constructs_from_temporary(const A&, D&));
>> +SA(!__reference_constructs_from_temporary(const A&, const D&));
>> +SA(!__reference_constructs_from_temporary(const A&, D&&));
>> +SA(!__reference_constructs_from_temporary(A&&, D));
>> +SA(!__reference_constructs_from_temporary(A&&, D&));
>> +SA(!__reference_constructs_from_temporary(A&&, const D&));
>> +SA(!__reference_constructs_from_temporary(A&&, D&&));
>> +
>> +SA(!__reference_constructs_from_temporary(A, E));
>> +/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
>> +   With the former, we get A& a = E::operator A& (&TARGET_EXPR 
>> <D.2715, {}>)
>> +   so we're not binding the reference to a temporary, although there is
>> +   a temporary involved.  So the result is false in both copy- and 
>> direct-
>> +   init, albeit for different reasons!  */
>> +SA(!__reference_constructs_from_temporary(A&, E));
>> +// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
>> +SA(!__reference_constructs_from_temporary(A&, E&));
>> +SA(!__reference_constructs_from_temporary(A&, const E&));
>> +SA(!__reference_constructs_from_temporary(A&, E&&));
>> +// direct-init:
>> +// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR 
>> <D.2720, {}>)
>> +SA(!__reference_constructs_from_temporary(const A&, E));
>> +SA(!__reference_constructs_from_temporary(const A&, E&));
>> +SA(!__reference_constructs_from_temporary(const A&, const E&));
>> +SA(!__reference_constructs_from_temporary(const A&, E&&));
>> +SA(!__reference_constructs_from_temporary(A&&, E));
>> +SA(!__reference_constructs_from_temporary(A&&, E&));
>> +SA(!__reference_constructs_from_temporary(A&&, const E&));
>> +SA(!__reference_constructs_from_temporary(A&&, E&&));
>> +
>> +SA(!__reference_constructs_from_temporary(A, F));
>> +// A& a1(F{}); and A& a2 = F{}; both invalid.
>> +SA(!__reference_constructs_from_temporary(A&, F));
>> +SA(!__reference_constructs_from_temporary(A&, F&));
>> +SA(!__reference_constructs_from_temporary(A&, const F&));
>> +SA(!__reference_constructs_from_temporary(A&, F&&));
>> +SA(!__reference_constructs_from_temporary(const A&, F));
>> +SA(!__reference_constructs_from_temporary(const A&, F&));
>> +SA(!__reference_constructs_from_temporary(const A&, const F&));
>> +SA(!__reference_constructs_from_temporary(const A&, F&&));
>> +SA(!__reference_constructs_from_temporary(A&&, F));
>> +SA(!__reference_constructs_from_temporary(A&&, F&));
>> +SA(!__reference_constructs_from_temporary(A&&, const F&));
>> +SA(!__reference_constructs_from_temporary(A&&, F&&));
>> +
>> +/* This is where _converts_ and _constructs_ will differ:
>> +   in direct-init we use G::operator int&& (no temporary),
>> +   but in copy-init we use G::operator int, where a temporary is created
>> +   to be bound to int&&.  */
>> +SA(!__reference_constructs_from_temporary(int&&, G));
>> +// Similar to the previous one.
>> +SA(!__reference_constructs_from_temporary(const int&, H));
>> +/* And here I've switched the explicit-ness.  In both copy- and 
>> direct-init
>> +   we call operator int&, so no temporary.  */
>> +SA(!__reference_constructs_from_temporary(int&&, G2));
>> +SA(!__reference_constructs_from_temporary(const int&, H2));
>> +
>> +SA(!__reference_constructs_from_temporary(const Base&, Der));
>> +
>> +// This fails because std::is_constructible_v<int&&, id<int[3]>> is 
>> false.
>> +SA(!__reference_constructs_from_temporary(int&&, id<int[3]>));
>> +
>> +// Arrays.
>> +SA(!__reference_constructs_from_temporary(int, int[]));
>> +SA(!__reference_constructs_from_temporary(int[], int[]));
>> +SA(!__reference_constructs_from_temporary(int&, int[]));
>> +SA(!__reference_constructs_from_temporary(int&&, int[]));
>> +SA(!__reference_constructs_from_temporary(const int&, int[]));
>> diff --git 
>> a/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C 
>> b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
>> new file mode 100644
>> index 00000000000..90196c38742
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
>> @@ -0,0 +1,214 @@
>> +// P2255R2
>> +// PR c++/104477
>> +// { dg-do compile { target c++11 } }
>> +
>> +#define SA(X) static_assert((X),#X)
>> +
>> +struct A { A(); };
>> +struct B { operator A(); };
>> +struct C { explicit C(); };
>> +struct D { explicit operator A(); };
>> +struct E { explicit operator A&(); };
>> +struct F { explicit operator A&&(); };
>> +// Could use a class template with explicit(bool), but then this 
>> would need
>> +// C++20.
>> +struct G {
>> +  operator int();
>> +  explicit operator int&&();
>> +};
>> +struct G2 {
>> +  explicit operator int();
>> +  operator int&&();
>> +};
>> +struct H {
>> +  operator int();
>> +  explicit operator int&();
>> +};
>> +struct H2 {
>> +  explicit operator int();
>> +  operator int&();
>> +};
>> +
>> +struct Base { };
>> +struct Der : Base { };
>> +
>> +template<typename T, typename RT>
>> +struct morph {
>> +  mutable T val{};
>> +  operator RT() const { return static_cast<RT>(val); }
>> +};
>> +
>> +template<typename T> using id = T;
>> +
>> +// Built-in types.
>> +SA(!__reference_converts_from_temporary(int, int));
>> +SA(!__reference_converts_from_temporary(int&, void));
>> +SA(!__reference_converts_from_temporary(int&, const void));
>> +SA(!__reference_converts_from_temporary(int&, volatile void));
>> +SA(!__reference_converts_from_temporary(int&, const volatile void));
>> +SA(!__reference_converts_from_temporary(void, void));
>> +SA(!__reference_converts_from_temporary(void, int));
>> +SA(!__reference_converts_from_temporary(int&, int));
>> +SA(!__reference_converts_from_temporary(int&, int&));
>> +SA(!__reference_converts_from_temporary(int&, int&&));
>> +SA(!__reference_converts_from_temporary(int&, long));
>> +// non-const lvalue reference to type 'int' cannot bind to a value of 
>> unrelated type 'long'
>> +SA(!__reference_converts_from_temporary(int&, long&));
>> +SA(!__reference_converts_from_temporary(int&, long&&));
>> +SA( __reference_converts_from_temporary(const int&, int));
>> +SA(!__reference_converts_from_temporary(const int&, int&));
>> +SA(!__reference_converts_from_temporary(const int&, const int&));
>> +SA(!__reference_converts_from_temporary(const int&, int&&));
>> +SA( __reference_converts_from_temporary(const int&, long));
>> +SA( __reference_converts_from_temporary(const int&, long&));
>> +SA( __reference_converts_from_temporary(const int&, long&&));
>> +SA( __reference_converts_from_temporary(int&&, int));
>> +SA(!__reference_converts_from_temporary(int&&, int&));
>> +SA(!__reference_converts_from_temporary(int&&, int&&));
>> +SA( __reference_converts_from_temporary(int&&, long));
>> +SA( __reference_converts_from_temporary(int&&, long&));
>> +SA( __reference_converts_from_temporary(int&&, long&&));
>> +SA(!__reference_converts_from_temporary(unsigned int&, double));
>> +SA(!__reference_converts_from_temporary(volatile int&, int));
>> +SA(!__reference_converts_from_temporary(const volatile int&, int));
>> +SA(!__reference_converts_from_temporary(volatile int&, int&));
>> +SA(!__reference_converts_from_temporary(const volatile int&, int&));
>> +SA(!__reference_converts_from_temporary(volatile int&, int&&));
>> +SA(!__reference_converts_from_temporary(const volatile int&, int&&));
>> +
>> +// Classes.
>> +SA(!__reference_converts_from_temporary(A, A));
>> +// A& r(A{}); doesn't construct.
>> +SA(!__reference_converts_from_temporary(A&, A));
>> +SA(!__reference_converts_from_temporary(A&, A&));
>> +SA(!__reference_converts_from_temporary(A&, A&&));
>> +// Here we get const struct A & r = (const struct A &) &D.2414;
>> +SA( __reference_converts_from_temporary(const A&, A));
>> +SA(!__reference_converts_from_temporary(const A&, A&));
>> +SA(!__reference_converts_from_temporary(const A&, const A&));
>> +SA(!__reference_converts_from_temporary(const A&, A&&));
>> +// Here we get struct A & r = (struct A &) &D.2439;
>> +SA( __reference_converts_from_temporary(A&&, A));
>> +SA(!__reference_converts_from_temporary(A&&, A&));
>> +SA(!__reference_converts_from_temporary(A&&, const A&));
>> +SA(!__reference_converts_from_temporary(A&&, A&&));
>> +
>> +SA(!__reference_converts_from_temporary(A, B));
>> +SA(!__reference_converts_from_temporary(A&, B));
>> +SA(!__reference_converts_from_temporary(A&, B&));
>> +SA(!__reference_converts_from_temporary(A&, const B&));
>> +SA(!__reference_converts_from_temporary(A&, B&&));
>> +SA( __reference_converts_from_temporary(const A&, B));
>> +SA( __reference_converts_from_temporary(const A&, B&));
>> +// Doesn't construct, so it's false.
>> +SA(!__reference_converts_from_temporary(const A&, const B&));
>> +SA( __reference_converts_from_temporary(const A&, B&&));
>> +SA( __reference_converts_from_temporary(A&&, B));
>> +SA( __reference_converts_from_temporary(A&&, B&));
>> +SA(!__reference_converts_from_temporary(A&&, const B&));
>> +SA( __reference_converts_from_temporary(A&&, B&&));
>> +
>> +SA(!__reference_converts_from_temporary(const A&, C));
>> +SA(!__reference_converts_from_temporary(const A&, C&));
>> +
>> +// Doesn't construct, so it's false.
>> +SA(!__reference_converts_from_temporary(int&, morph<int, int>));
>> +// Here we get
>> +//   const int & r2 = D.2580 = morph<int, int>::operator int
>> +//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
>> +SA( __reference_converts_from_temporary(const int&, morph<int, int>));
>> +SA(!__reference_converts_from_temporary(int&, morph<int, int&>));
>> +SA(!__reference_converts_from_temporary(int&, morph<int, const int&>));
>> +SA(!__reference_converts_from_temporary(int&, morph<int, int&&>));
>> +SA( __reference_converts_from_temporary(const int&, morph<long, 
>> long&>));
>> +
>> +// These are like const C& c(cref); so the explicit ctor C isn't a 
>> problem
>> +// even in copy-init context.  const C& r = {}; would be a different 
>> story.
>> +SA(!__reference_converts_from_temporary(C, C));
>> +SA(!__reference_converts_from_temporary(C&, C));
>> +SA(!__reference_converts_from_temporary(C&, C&));
>> +SA(!__reference_converts_from_temporary(C&, C&&));
>> +SA( __reference_converts_from_temporary(const C&, C));
>> +SA(!__reference_converts_from_temporary(const C&, C&));
>> +SA(!__reference_converts_from_temporary(const C&, const C&));
>> +SA(!__reference_converts_from_temporary(const C&, C&&));
>> +SA( __reference_converts_from_temporary(C&&, C));
>> +SA(!__reference_converts_from_temporary(C&&, C&));
>> +SA(!__reference_converts_from_temporary(C&&, const C&));
>> +SA(!__reference_converts_from_temporary(C&&, C&&));
>> +
>> +// These are all false ultimately because of CWG 2267, which we 
>> implement.
>> +SA(!__reference_converts_from_temporary(A, D));
>> +SA(!__reference_converts_from_temporary(A&, D));
>> +SA(!__reference_converts_from_temporary(A&, D&));
>> +SA(!__reference_converts_from_temporary(A&, const D&));
>> +SA(!__reference_converts_from_temporary(A&, D&&));
>> +SA(!__reference_converts_from_temporary(const A&, D));
>> +SA(!__reference_converts_from_temporary(const A&, D&));
>> +SA(!__reference_converts_from_temporary(const A&, const D&));
>> +SA(!__reference_converts_from_temporary(const A&, D&&));
>> +SA(!__reference_converts_from_temporary(A&&, D));
>> +SA(!__reference_converts_from_temporary(A&&, D&));
>> +SA(!__reference_converts_from_temporary(A&&, const D&));
>> +SA(!__reference_converts_from_temporary(A&&, D&&));
>> +
>> +SA(!__reference_converts_from_temporary(A, E));
>> +/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
>> +   With the former, we get A& a = E::operator A& (&TARGET_EXPR 
>> <D.2715, {}>)
>> +   so we're not binding the reference to a temporary, although there is
>> +   a temporary involved.  So the result is false in both copy- and 
>> direct-
>> +   init, albeit for different reasons!  */
>> +SA(!__reference_converts_from_temporary(A&, E));
>> +// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
>> +SA(!__reference_converts_from_temporary(A&, E&));
>> +SA(!__reference_converts_from_temporary(A&, const E&));
>> +SA(!__reference_converts_from_temporary(A&, E&&));
>> +// direct-init:
>> +// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR 
>> <D.2720, {}>)
>> +SA(!__reference_converts_from_temporary(const A&, E));
>> +SA(!__reference_converts_from_temporary(const A&, E&));
>> +SA(!__reference_converts_from_temporary(const A&, const E&));
>> +SA(!__reference_converts_from_temporary(const A&, E&&));
>> +SA(!__reference_converts_from_temporary(A&&, E));
>> +SA(!__reference_converts_from_temporary(A&&, E&));
>> +SA(!__reference_converts_from_temporary(A&&, const E&));
>> +SA(!__reference_converts_from_temporary(A&&, E&&));
>> +
>> +SA(!__reference_converts_from_temporary(A, F));
>> +// A& a1(F{}); and A& a2 = F{}; both invalid.
>> +SA(!__reference_converts_from_temporary(A&, F));
>> +SA(!__reference_converts_from_temporary(A&, F&));
>> +SA(!__reference_converts_from_temporary(A&, const F&));
>> +SA(!__reference_converts_from_temporary(A&, F&&));
>> +SA(!__reference_converts_from_temporary(const A&, F));
>> +SA(!__reference_converts_from_temporary(const A&, F&));
>> +SA(!__reference_converts_from_temporary(const A&, const F&));
>> +SA(!__reference_converts_from_temporary(const A&, F&&));
>> +SA(!__reference_converts_from_temporary(A&&, F));
>> +SA(!__reference_converts_from_temporary(A&&, F&));
>> +SA(!__reference_converts_from_temporary(A&&, const F&));
>> +SA(!__reference_converts_from_temporary(A&&, F&&));
>> +
>> +/* This is where _converts_ and _constructs_ will differ:
>> +   in direct-init we use G::operator int&& (no temporary),
>> +   but in copy-init we use G::operator int, where a temporary is created
>> +   to be bound to int&&.  */
>> +SA( __reference_converts_from_temporary(int&&, G));
>> +// Similar to the previous one.
>> +SA( __reference_converts_from_temporary(const int&, H));
>> +/* And here I've switched the explicit-ness.  In both copy- and 
>> direct-init
>> +   we call operator int&, so no temporary.  */
>> +SA(!__reference_converts_from_temporary(int&&, G2));
>> +SA(!__reference_converts_from_temporary(const int&, H2));
>> +
>> +SA(!__reference_converts_from_temporary(const Base&, Der));
>> +
>> +// This fails because std::is_constructible_v<int&&, id<int[3]>> is 
>> false.
>> +SA(!__reference_converts_from_temporary(int&&, id<int[3]>));
>> +
>> +// Arrays.
>> +SA(!__reference_converts_from_temporary(int, int[]));
>> +SA(!__reference_converts_from_temporary(int[], int[]));
>> +SA(!__reference_converts_from_temporary(int&, int[]));
>> +SA(!__reference_converts_from_temporary(int&&, int[]));
>> +SA(!__reference_converts_from_temporary(const int&, int[]));
>> diff --git a/libstdc++-v3/include/std/type_traits 
>> b/libstdc++-v3/include/std/type_traits
>> index 2572d8edd69..bbeb049e1f9 100644
>> --- a/libstdc++-v3/include/std/type_traits
>> +++ b/libstdc++-v3/include/std/type_traits
>> @@ -3508,6 +3508,45 @@ template<typename _Ret, typename _Fn, 
>> typename... _Args>
>>     template<typename _Tp>
>>       inline constexpr bool is_scoped_enum_v = 
>> is_scoped_enum<_Tp>::value;
>> +#define __cpp_lib_reference_from_temporary 202202L
>> +
>> +  /// True if _Tp is a reference type, a _Up value can be bound to 
>> _Tp in
>> +  /// direct-initialization, and a temporary object would be bound to
>> +  /// the reference, false otherwise.
>> +  /// @since C++23
>> +  template<typename _Tp, typename _Up>
>> +    struct reference_constructs_from_temporary
>> +    : public 
>> __bool_constant<__reference_constructs_from_temporary(_Tp, _Up)>
>> +    {
>> +      
>> static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
>> +            && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
>> +    "template argument must be a complete class or an unbounded array");
>> +    };
>> +
>> +  /// True if _Tp is a reference type, a _Up value can be bound to 
>> _Tp in
>> +  /// copy-initialization, and a temporary object would be bound to
>> +  /// the reference, false otherwise.
>> +  /// @since C++23
>> +  template<typename _Tp, typename _Up>
>> +    struct reference_converts_from_temporary
>> +    : public __bool_constant<__reference_converts_from_temporary(_Tp, 
>> _Up)>
>> +    {
>> +      
>> static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
>> +            && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
>> +    "template argument must be a complete class or an unbounded array");
>> +    };
>> +
>> +  /// @ingroup variable_templates
>> +  /// @since C++23
>> +  template<typename _Tp, typename _Up>
>> +    inline constexpr bool reference_constructs_from_temporary_v
>> +      = reference_constructs_from_temporary<_Tp, _Up>::value;
>> +
>> +  /// @ingroup variable_templates
>> +  /// @since C++23
>> +  template<typename _Tp, typename _Up>
>> +    inline constexpr bool reference_converts_from_temporary_v
>> +      = reference_converts_from_temporary<_Tp, _Up>::value;
>>   #endif // C++23
>>   #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
>> diff --git a/libstdc++-v3/include/std/version 
>> b/libstdc++-v3/include/std/version
>> index 22280e1a349..5edca2f3007 100644
>> --- a/libstdc++-v3/include/std/version
>> +++ b/libstdc++-v3/include/std/version
>> @@ -300,10 +300,11 @@
>>   #endif
>>   #if __cplusplus > 202002L
>> -// c++2b
>> +// c++23
>>   #define __cpp_lib_byteswap 202110L
>>   #define __cpp_lib_constexpr_typeinfo 202106L
>>   #define __cpp_lib_is_scoped_enum 202011L
>> +#define __cpp_lib_reference_from_temporary 202202L
>>   #if _GLIBCXX_HOSTED
>>   #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
>> @@ -335,7 +336,7 @@
>>   #define __cpp_lib_to_underlying 202102L
>>   #define __cpp_lib_unreachable 202202L
>>   #endif
>> -#endif // C++2b
>> +#endif // C++23
>>   #endif // C++20
>>   #endif // C++17
>>   #endif // C++14
>> diff --git 
>> a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc 
>> b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
>> new file mode 100644
>> index 00000000000..2f62e54d46d
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
>> @@ -0,0 +1,110 @@
>> +// Copyright (C) 2022 Free Software Foundation, Inc.
>> +//
>> +// This file is part of the GNU ISO C++ Library.  This library is free
>> +// software; you can redistribute it and/or modify it under the
>> +// terms of the GNU General Public License as published by the
>> +// Free Software Foundation; either version 3, or (at your option)
>> +// any later version.
>> +
>> +// This library is distributed in the hope that it will be useful,
>> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +// GNU General Public License for more details.
>> +
>> +// You should have received a copy of the GNU General Public License 
>> along
>> +// with this library; see the file COPYING3.  If not see
>> +// <http://www.gnu.org/licenses/>.
>> +
>> +// { dg-options "-std=gnu++23" }
>> +// { dg-do compile { target c++23 } }
>> +
>> +#include <type_traits>
>> +#include <testsuite_tr1.h>
>> +
>> +#ifndef __cpp_lib_reference_from_temporary
>> +# error "Feature test macro for reference_from_temporary is missing 
>> in <version>"
>> +#elif __cpp_lib_reference_from_temporary < 202202L
>> +# error "Feature test macro for reference_from_temporary has wrong 
>> value in <version>"
>> +#endif
>> +
>> +void test01()
>> +{
>> +  using std::reference_constructs_from_temporary;
>> +  using std::reference_converts_from_temporary;
>> +  using namespace __gnu_test;
>> +
>> +  struct A { A(); };
>> +
>> +  struct B {
>> +    operator int();
>> +    explicit operator int&&();
>> +  };
>> +
>> +  struct C {
>> +    operator int();
>> +    explicit operator int&();
>> +  };
>> +
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int, int>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, void>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, const volatile void>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> void, void>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, int>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, int&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, int&&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, long>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, long&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, long&&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const int&, int>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const int&, int&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const int&, int&&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const int&, long>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const int&, long&>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const int&, long&&>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&&, int>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&&, int&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&&, int&&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&&, long>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&&, long&>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&&, long&&>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const A&, A>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const A&, A&&>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> A&&, A>(true), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&, int[]>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const int&, int[]>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&&, int[]>(false), "");
>> +
>> +  static_assert(test_property<reference_converts_from_temporary, int, 
>> int>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, void>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, const volatile void>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> void, void>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, int>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, int&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, int&&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, long>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, long&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, long&&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const int&, int>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const int&, int&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const int&, int&&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const int&, long>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const int&, long&>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const int&, long&&>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&&, int>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&&, int&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&&, int&&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&&, long>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&&, long&>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&&, long&&>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const A&, A>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const A&, A&&>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, A&&, 
>> A>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&, int[]>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const int&, int[]>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&&, int[]>(false), "");
>> +
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> int&&, B>(false), "");
>> +  static_assert(test_property<reference_constructs_from_temporary, 
>> const int&, C>(false), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> int&&, B>(true), "");
>> +  static_assert(test_property<reference_converts_from_temporary, 
>> const int&, C>(true), "");
>> +}
>> diff --git 
>> a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc 
>> b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
>> new file mode 100644
>> index 00000000000..65770754299
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
>> @@ -0,0 +1,28 @@
>> +// Copyright (C) 2022 Free Software Foundation, Inc.
>> +//
>> +// This file is part of the GNU ISO C++ Library.  This library is free
>> +// software; you can redistribute it and/or modify it under the
>> +// terms of the GNU General Public License as published by the
>> +// Free Software Foundation; either version 3, or (at your option)
>> +// any later version.
>> +
>> +// This library is distributed in the hope that it will be useful,
>> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +// GNU General Public License for more details.
>> +
>> +// You should have received a copy of the GNU General Public License 
>> along
>> +// with this library; see the file COPYING3.  If not see
>> +// <http://www.gnu.org/licenses/>.
>> +
>> +// { dg-options "-std=gnu++23" }
>> +// { dg-do compile { target c++23 } }
>> +
>> +#include <type_traits>
>> +#include <string>
>> +
>> +void test01()
>> +{
>> +  static_assert(std::reference_converts_from_temporary_v<const 
>> std::string&, const char*>);
>> +  static_assert(std::reference_constructs_from_temporary_v<const 
>> std::string&, const char*>);
>> +}
>> diff --git 
>> a/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc 
>> b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
>> new file mode 100644
>> index 00000000000..f56e7c0dabc
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
>> @@ -0,0 +1,27 @@
>> +// Copyright (C) 2022 Free Software Foundation, Inc.
>> +//
>> +// This file is part of the GNU ISO C++ Library.  This library is free
>> +// software; you can redistribute it and/or modify it under the
>> +// terms of the GNU General Public License as published by the
>> +// Free Software Foundation; either version 3, or (at your option)
>> +// any later version.
>> +
>> +// This library is distributed in the hope that it will be useful,
>> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +// GNU General Public License for more details.
>> +
>> +// You should have received a copy of the GNU General Public License 
>> along
>> +// with this library; see the file COPYING3.  If not see
>> +// <http://www.gnu.org/licenses/>.
>> +
>> +// { dg-options "-std=gnu++23" }
>> +// { dg-do compile { target c++23 } }
>> +
>> +#include <version>
>> +
>> +#ifndef __cpp_lib_reference_from_temporary
>> +# error "Feature test macro for reference_from_temporary is missing 
>> in <version>"
>> +#elif __cpp_lib_reference_from_temporary < 202202L
>> +# error "Feature test macro for reference_from_temporary has wrong 
>> value in <version>"
>> +#endif
>> diff --git 
>> a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc 
>> b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
>> index 9b3957f7d47..2b03ad7067d 100644
>> --- a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
>> +++ b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
>> @@ -346,3 +346,17 @@ static_assert(disjunction_v<false_type, false_type,
>>                 true_type>, "");
>>   static_assert(!disjunction_v<false_type, false_type,
>>                 false_type>, "");
>> +#if __cpp_lib_reference_from_temporary >= 202202L
>> +static_assert(std::reference_converts_from_temporary_v<int&&, int>
>> +          && std::reference_converts_from_temporary_v<const int&, int>
>> +          && !std::reference_converts_from_temporary_v<int&&, int&&>
>> +          && !std::reference_converts_from_temporary_v<const int&, 
>> int&&>
>> +          && std::reference_converts_from_temporary_v<int&&, long&&>
>> +          && std::reference_converts_from_temporary_v<int&&, long>, "");
>> +static_assert(std::reference_constructs_from_temporary_v<int&&, int>
>> +          && std::reference_constructs_from_temporary_v<const int&, int>
>> +          && !std::reference_constructs_from_temporary_v<int&&, int&&>
>> +          && !std::reference_constructs_from_temporary_v<const int&, 
>> int&&>
>> +          && std::reference_constructs_from_temporary_v<int&&, long&&>
>> +          && std::reference_constructs_from_temporary_v<int&&, long>, 
>> "");
>> +#endif
>>
>> base-commit: 55bb77b50a5ec520f28978ac3fdac1983200e1f7
> 


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v3] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-12 20:10 ` [PATCH] " Jason Merrill
  2022-07-12 20:15   ` Jason Merrill
@ 2022-07-14 17:41   ` Marek Polacek
  1 sibling, 0 replies; 15+ messages in thread
From: Marek Polacek @ 2022-07-14 17:41 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches, libstdc++, Jonathan Wakely

On Tue, Jul 12, 2022 at 04:10:08PM -0400, Jason Merrill wrote:
> On 7/8/22 13:41, Marek Polacek wrote:
> > +bool
> > +reference_from_temporary (tree to, tree from, bool direct_init_p)
> > +{
> > +  /* Check is_reference<T>.  */
> > +  if (!TYPE_REF_P (to))
> > +    return false;
> > +  /* Check is_constructible<T, U>.
> > +     ??? This check doesn't seem to be necessary; if T isn't constructible
> > +     from U, we won't be able to create a conversion.  */
> > +  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
> > +    return false;
> 
> I agree with the comment, did you try leaving this out?  If it stays I'd
> think it needs to consider direct_init_p.

I did, it doesn't break anything, so in this version I dropped the is_xible
call entirely.
 
> > @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
> >     if (TYPE_REF_P (type))
> >       {
> > -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
> > +      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type, expr))
> >   	{
> >   	  auto_diagnostic_group d;
> >   	  if (warning_at (loc, OPT_Wrange_loop_construct,
> > @@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
> >     tree rtype = cp_build_reference_type (type, /*rval*/false);
> >     /* If we could initialize the reference directly, it wouldn't involve any
> >        copies.  */
> > -  if (!ref_conv_binds_directly_p (rtype, expr))
> > +  if (ref_conv_binds_to_temporary_p (rtype, expr))
> >       return;
> 
> I think this case wants the old handling of invalid conversions you
> mentioned in your intro; we don't want to suggest changing to a reference if
> that's ill-formed.

Right.  I've changed the return type to 'tristate', so that we can properly
distinguish between true/false/dunno.
 
> In passing we might change the comment to "If we can initialize a reference
> directly, suggest that to avoid the copy." and move it above the rtype
> declaration.

Done.
 
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch implements C++23 P2255R2, which adds two new type traits to
detect reference binding to a temporary.  They can be used to detect code
like

  std::tuple<const std::string&> t("meow");

which is incorrect because it always creates a dangling reference, because
the std::string temporary is created inside the selected constructor of
std::tuple, and not outside it.

There are two new compiler builtins, __reference_constructs_from_temporary
and __reference_converts_from_temporary.  The former is used to simulate
direct- and the latter copy-initialization context.  But I had a hard time
finding a test where there's actually a difference.  Under DR 2267, both
of these are invalid:

  struct A { } a;
  struct B { explicit B(const A&); };
  const B &b1{a};
  const B &b2(a);

so I had to peruse [over.match.ref], and eventually realized that the
difference can be seen here:

  struct G {
    operator int(); // #1
    explicit operator int&&(); // #2
  };

int&& r1(G{}); // use #2 (no temporary)
int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)

The implementation itself was rather straightforward because we already
have the conv_binds_ref_to_prvalue function.  The main function here is
reference_from_temporary.
I've changed the return type of ref_conv_binds_directly to tristate, because
previously the function didn't distinguish between an invalid conversion and
one that binds to a prvalue.  Since it no longer returns a bool, I removed
the _p suffix.

The patch also adds the relevant class and variable templates to <type_traits>.

	PR c++/104477

gcc/c-family/ChangeLog:

	* c-common.cc (c_common_reswords): Add
	__reference_constructs_from_temporary and
	__reference_converts_from_temporary.
	* c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
	RID_REF_CONVERTS_FROM_TEMPORARY.

gcc/cp/ChangeLog:

	* call.cc (ref_conv_binds_directly_p): Rename to ...
	(ref_conv_binds_directly): ... this.  Add a new bool parameter.  Change
	the return type to tristate.
	* constraint.cc (diagnose_trait_expr): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	* cp-tree.h: Include "tristate.h".
	(enum cp_trait_kind): Add CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
	and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	(ref_conv_binds_directly_p): Rename to ...
	(ref_conv_binds_directly): ... this.
	(reference_from_temporary): Declare.
	* cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	* method.cc (reference_from_temporary): New.
	* parser.cc (cp_parser_primary_expression): Handle
	RID_REF_CONSTRUCTS_FROM_TEMPORARY and RID_REF_CONVERTS_FROM_TEMPORARY.
	(cp_parser_trait_expr): Likewise.
	(warn_for_range_copy): Adjust to call ref_conv_binds_directly.
	* semantics.cc (trait_expr_value): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	(finish_trait_expr): Likewise.

libstdc++-v3/ChangeLog:

	* include/std/type_traits (reference_constructs_from_temporary,
	reference_converts_from_temporary): New class templates.
	(reference_constructs_from_temporary_v,
	reference_converts_from_temporary_v): New variable templates.
	(__cpp_lib_reference_from_temporary): Define for C++23.
	* include/std/version (__cpp_lib_reference_from_temporary): Define for
	C++23.
	* testsuite/20_util/variable_templates_for_traits.cc: Test
	reference_constructs_from_temporary_v and
	reference_converts_from_temporary_v.
	* testsuite/20_util/reference_from_temporary/value.cc: New test.
	* testsuite/20_util/reference_from_temporary/value2.cc: New test.
	* testsuite/20_util/reference_from_temporary/version.cc: New test.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/reference_constructs_from_temporary1.C: New test.
	* g++.dg/ext/reference_converts_from_temporary1.C: New test.
---
 gcc/c-family/c-common.cc                      |   4 +
 gcc/c-family/c-common.h                       |   2 +
 gcc/cp/call.cc                                |  20 +-
 gcc/cp/constraint.cc                          |   8 +
 gcc/cp/cp-tree.h                              |   8 +-
 gcc/cp/cxx-pretty-print.cc                    |   6 +
 gcc/cp/method.cc                              |  25 ++
 gcc/cp/parser.cc                              |  36 +--
 gcc/cp/semantics.cc                           |   8 +
 .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
 .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
 libstdc++-v3/include/std/type_traits          |  39 ++++
 libstdc++-v3/include/std/version              |   5 +-
 .../20_util/reference_from_temporary/value.cc | 110 +++++++++
 .../reference_from_temporary/value2.cc        |  28 +++
 .../reference_from_temporary/version.cc       |  27 +++
 .../20_util/variable_templates_for_traits.cc  |  14 ++
 17 files changed, 744 insertions(+), 24 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 1b8e73f7bc5..655c3aefee6 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
   { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
   { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
   { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY },
+  { "__reference_constructs_from_temporary", RID_REF_CONSTRUCTS_FROM_TEMPORARY,
+					D_CXXONLY },
+  { "__reference_converts_from_temporary", RID_REF_CONVERTS_FROM_TEMPORARY,
+					D_CXXONLY },
 
   /* C++ transactional memory.  */
   { "synchronized",	RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c0900848965..f9064393b4e 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -184,6 +184,8 @@ enum rid
   RID_IS_UNION,                RID_UNDERLYING_TYPE,
   RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
   RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
+  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
+  RID_REF_CONVERTS_FROM_TEMPORARY,
 
   /* C++11 */
   RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index fc98552fda2..191c68cdcfd 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9109,21 +9109,27 @@ conv_binds_ref_to_prvalue (conversion *c)
   return conv_is_prvalue (next_conversion (c));
 }
 
-/* True iff converting EXPR to a reference type TYPE does not involve
-   creating a temporary.  */
+/* Return tristate::TS_TRUE if converting EXPR to a reference type TYPE does
+   not involve creating a temporary.  Return tristate::TS_FALSE if converting
+   EXPR to a reference type TYPE binds the reference to a temporary.  If the
+   conversion is invalid or bad, return tristate::TS_UNKNOWN.  DIRECT_INIT_P
+   says whether the conversion should be done in direct- or copy-initialization
+   context.  */
 
-bool
-ref_conv_binds_directly_p (tree type, tree expr)
+tristate
+ref_conv_binds_directly (tree type, tree expr, bool direct_init_p /*= false*/)
 {
   gcc_assert (TYPE_REF_P (type));
 
   /* Get the high-water mark for the CONVERSION_OBSTACK.  */
   void *p = conversion_obstack_alloc (0);
 
+  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
   conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
-					  /*c_cast_p=*/false,
-					  LOOKUP_IMPLICIT, tf_none);
-  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
+					  /*c_cast_p=*/false, flags, tf_none);
+  tristate ret (tristate::TS_UNKNOWN);
+  if (conv && !conv->bad_p)
+    ret = tristate (!conv_binds_ref_to_prvalue (conv));
 
   /* Free all the conversions we allocated.  */
   obstack_free (&conversion_obstack, p);
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index f2137eb7aa1..568318f0ba1 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3697,6 +3697,14 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
       inform (loc, "  %qT does not have unique object representations", t1);
       break;
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      inform (loc, "  %qT is not a reference that binds to a temporary "
+	      "object of type %qT (direct-initialization)", t1, t2);
+      break;
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      inform (loc, "  %qT is not a reference that binds to a temporary "
+	      "object of type %qT (copy-initialization)", t1, t2);
+      break;
     case CPTK_BASES:
     case CPTK_DIRECT_BASES:
     case CPTK_UNDERLYING_TYPE:
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index bec98aa2ac3..95fc856ed17 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tm.h"
 #include "hard-reg-set.h"
 #include "function.h"
+#include "tristate.h"
 
 /* In order for the format checking to accept the C++ front end
    diagnostic framework extensions, you must include this file before
@@ -1397,7 +1398,9 @@ enum cp_trait_kind
   CPTK_IS_ASSIGNABLE,
   CPTK_IS_CONSTRUCTIBLE,
   CPTK_IS_NOTHROW_ASSIGNABLE,
-  CPTK_IS_NOTHROW_CONSTRUCTIBLE
+  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
+  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
+  CPTK_REF_CONVERTS_FROM_TEMPORARY
 };
 
 /* The types that we are processing.  */
@@ -6520,7 +6523,7 @@ extern bool sufficient_parms_p			(const_tree);
 extern tree type_decays_to			(tree);
 extern tree extract_call_expr			(tree);
 extern tree build_trivial_dtor_call		(tree, bool = false);
-extern bool ref_conv_binds_directly_p		(tree, tree);
+extern tristate ref_conv_binds_directly		(tree, tree, bool = false);
 extern tree build_user_type_conversion		(tree, tree, int,
 						 tsubst_flags_t);
 extern tree build_new_function_call		(tree, vec<tree, va_gc> **,
@@ -7105,6 +7108,7 @@ extern tree forward_parm			(tree);
 extern bool is_trivially_xible			(enum tree_code, tree, tree);
 extern bool is_nothrow_xible			(enum tree_code, tree, tree);
 extern bool is_xible				(enum tree_code, tree, tree);
+extern bool reference_from_temporary		(tree, tree, bool);
 extern tree get_defaulted_eh_spec		(tree, tsubst_flags_t = tf_warning_or_error);
 extern bool maybe_explain_implicit_delete	(tree);
 extern void explain_implicit_non_constexpr	(tree);
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 7e4db2e413b..44590830a61 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t)
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
       pp_cxx_ws_string (pp, "__is_nothrow_constructible");
       break;
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
+      break;
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
+      break;
 
     default:
       gcc_unreachable ();
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 0dffd648b0b..c94c15a6ac0 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -2211,6 +2211,31 @@ is_xible (enum tree_code code, tree to, tree from)
   return !!expr;
 }
 
+/* Return true iff conjunction_v<is_reference<T>, is_constructible<T, U>> is
+   true, and the initialization
+     T t(VAL<U>); // DIRECT_INIT_P
+   or
+     T t = VAL<U>; // !DIRECT_INIT_P
+   binds t to a temporary object whose lifetime is extended.
+   VAL<T> is defined in [meta.unary.prop]:
+   -- If T is a reference or function type, VAL<T> is an expression with the
+   same type and value category as declval<T>().
+   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
+
+bool
+reference_from_temporary (tree to, tree from, bool direct_init_p)
+{
+  /* Check is_reference<T>.  */
+  if (!TYPE_REF_P (to))
+    return false;
+  /* We don't check is_constructible<T, U>: if T isn't constructible
+     from U, we won't be able to create a conversion.  */
+  tree val = build_stub_object (from);
+  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
+    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) : rvalue (val);
+  return ref_conv_binds_directly (to, val, direct_init_p).is_false ();
+}
+
 /* Categorize various special_function_kinds.  */
 #define SFK_CTOR_P(sfk) \
   ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index bf9ea3685f8..4f67441eeb1 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
 	case RID_IS_CONSTRUCTIBLE:
 	case RID_IS_NOTHROW_ASSIGNABLE:
 	case RID_IS_NOTHROW_CONSTRUCTIBLE:
+	case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
+	case RID_REF_CONVERTS_FROM_TEMPORARY:
 	  return cp_parser_trait_expr (parser, token->keyword);
 
 	// C++ concepts
@@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
       kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
       variadic = true;
       break;
+    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
+      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
+      binary = true;
+      break;
+    case RID_REF_CONVERTS_FROM_TEMPORARY:
+      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
+      binary = true;
+      break;
     default:
       gcc_unreachable ();
     }
@@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
 
   if (TYPE_REF_P (type))
     {
-      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
+      if (glvalue_p (expr) && ref_conv_binds_directly (type, expr).is_false ())
 	{
 	  auto_diagnostic_group d;
 	  if (warning_at (loc, OPT_Wrange_loop_construct,
@@ -13839,20 +13849,20 @@ warn_for_range_copy (tree decl, tree expr)
 	  && trivially_copyable_p (type)))
     return;
 
+  /* If we can initialize a reference directly, suggest that to avoid the
+     copy.  */
   tree rtype = cp_build_reference_type (type, /*rval*/false);
-  /* If we could initialize the reference directly, it wouldn't involve any
-     copies.  */
-  if (!ref_conv_binds_directly_p (rtype, expr))
-    return;
-
-  auto_diagnostic_group d;
-  if (warning_at (loc, OPT_Wrange_loop_construct,
-		  "loop variable %qD creates a copy from type %qT",
-		  decl, type))
+  if (ref_conv_binds_directly (rtype, expr).is_true ())
     {
-      gcc_rich_location richloc (loc);
-      richloc.add_fixit_insert_before ("&");
-      inform (&richloc, "use reference type to prevent copying");
+      auto_diagnostic_group d;
+      if (warning_at (loc, OPT_Wrange_loop_construct,
+		      "loop variable %qD creates a copy from type %qT",
+		      decl, type))
+	{
+	  gcc_rich_location richloc (loc);
+	  richloc.add_fixit_insert_before ("&");
+	  inform (&richloc, "use reference type to prevent copying");
+	}
     }
 }
 
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 2344b5eea00..396a9f6b44e 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -12007,6 +12007,12 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
       return is_nothrow_xible (INIT_EXPR, type1, type2);
 
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      return reference_from_temporary (type1, type2, /*direct_init=*/true);
+
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      return reference_from_temporary (type1, type2, /*direct_init=*/false);
+
     default:
       gcc_unreachable ();
       return false;
@@ -12088,6 +12094,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
     case CPTK_IS_NOTHROW_ASSIGNABLE:
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
       if (!check_trait_type (type1)
 	  || !check_trait_type (type2))
 	return error_mark_node;
diff --git a/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
new file mode 100644
index 00000000000..76de905a35d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
@@ -0,0 +1,214 @@
+// P2255R2
+// PR c++/104477
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+struct A { A(); };
+struct B { operator A(); };
+struct C { explicit C(); };
+struct D { explicit operator A(); };
+struct E { explicit operator A&(); };
+struct F { explicit operator A&&(); };
+// Could use a class template with explicit(bool), but then this would need
+// C++20.
+struct G {
+  operator int();
+  explicit operator int&&();
+};
+struct G2 {
+  explicit operator int();
+  operator int&&();
+};
+struct H {
+  operator int();
+  explicit operator int&();
+};
+struct H2 {
+  explicit operator int();
+  operator int&();
+};
+
+struct Base { };
+struct Der : Base { };
+
+template<typename T, typename RT>
+struct morph {
+  mutable T val{};
+  operator RT() const { return static_cast<RT>(val); }
+};
+
+template<typename T> using id = T;
+
+// Built-in types.
+SA(!__reference_constructs_from_temporary(int, int));
+SA(!__reference_constructs_from_temporary(int&, void));
+SA(!__reference_constructs_from_temporary(int&, const void));
+SA(!__reference_constructs_from_temporary(int&, volatile void));
+SA(!__reference_constructs_from_temporary(int&, const volatile void));
+SA(!__reference_constructs_from_temporary(void, void));
+SA(!__reference_constructs_from_temporary(void, int));
+SA(!__reference_constructs_from_temporary(int&, int));
+SA(!__reference_constructs_from_temporary(int&, int&));
+SA(!__reference_constructs_from_temporary(int&, int&&));
+SA(!__reference_constructs_from_temporary(int&, long));
+// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
+SA(!__reference_constructs_from_temporary(int&, long&));
+SA(!__reference_constructs_from_temporary(int&, long&&));
+SA( __reference_constructs_from_temporary(const int&, int));
+SA(!__reference_constructs_from_temporary(const int&, int&));
+SA(!__reference_constructs_from_temporary(const int&, const int&));
+SA(!__reference_constructs_from_temporary(const int&, int&&));
+SA( __reference_constructs_from_temporary(const int&, long));
+SA( __reference_constructs_from_temporary(const int&, long&));
+SA( __reference_constructs_from_temporary(const int&, long&&));
+SA( __reference_constructs_from_temporary(int&&, int));
+SA(!__reference_constructs_from_temporary(int&&, int&));
+SA(!__reference_constructs_from_temporary(int&&, int&&));
+SA( __reference_constructs_from_temporary(int&&, long));
+SA( __reference_constructs_from_temporary(int&&, long&));
+SA( __reference_constructs_from_temporary(int&&, long&&));
+SA(!__reference_constructs_from_temporary(unsigned int&, double));
+SA(!__reference_constructs_from_temporary(volatile int&, int));
+SA(!__reference_constructs_from_temporary(const volatile int&, int));
+SA(!__reference_constructs_from_temporary(volatile int&, int&));
+SA(!__reference_constructs_from_temporary(const volatile int&, int&));
+SA(!__reference_constructs_from_temporary(volatile int&, int&&));
+SA(!__reference_constructs_from_temporary(const volatile int&, int&&));
+
+// Classes.
+SA(!__reference_constructs_from_temporary(A, A));
+// A& r(A{}); doesn't construct.
+SA(!__reference_constructs_from_temporary(A&, A));
+SA(!__reference_constructs_from_temporary(A&, A&));
+SA(!__reference_constructs_from_temporary(A&, A&&));
+// Here we get const struct A & r = (const struct A &) &D.2414;
+SA( __reference_constructs_from_temporary(const A&, A));
+SA(!__reference_constructs_from_temporary(const A&, A&));
+SA(!__reference_constructs_from_temporary(const A&, const A&));
+SA(!__reference_constructs_from_temporary(const A&, A&&));
+// Here we get struct A & r = (struct A &) &D.2439;
+SA( __reference_constructs_from_temporary(A&&, A));
+SA(!__reference_constructs_from_temporary(A&&, A&));
+SA(!__reference_constructs_from_temporary(A&&, const A&));
+SA(!__reference_constructs_from_temporary(A&&, A&&));
+
+SA(!__reference_constructs_from_temporary(A, B));
+SA(!__reference_constructs_from_temporary(A&, B));
+SA(!__reference_constructs_from_temporary(A&, B&));
+SA(!__reference_constructs_from_temporary(A&, const B&));
+SA(!__reference_constructs_from_temporary(A&, B&&));
+SA( __reference_constructs_from_temporary(const A&, B));
+SA( __reference_constructs_from_temporary(const A&, B&));
+// Doesn't construct, so it's false.
+SA(!__reference_constructs_from_temporary(const A&, const B&));
+SA( __reference_constructs_from_temporary(const A&, B&&));
+SA( __reference_constructs_from_temporary(A&&, B));
+SA( __reference_constructs_from_temporary(A&&, B&));
+SA(!__reference_constructs_from_temporary(A&&, const B&));
+SA( __reference_constructs_from_temporary(A&&, B&&));
+
+SA(!__reference_constructs_from_temporary(const A&, C));
+SA(!__reference_constructs_from_temporary(const A&, C&));
+
+// Doesn't construct, so it's false.
+SA(!__reference_constructs_from_temporary(int&, morph<int, int>));
+// Here we get
+//   const int & r2 = D.2580 = morph<int, int>::operator int
+//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
+SA( __reference_constructs_from_temporary(const int&, morph<int, int>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, int&>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, const int&>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, int&&>));
+SA( __reference_constructs_from_temporary(const int&, morph<long, long&>));
+
+// These are like const C& c(cref); so the explicit ctor C isn't a problem
+// even in copy-init context.  const C& r = {}; would be a different story.
+SA(!__reference_constructs_from_temporary(C, C));
+SA(!__reference_constructs_from_temporary(C&, C));
+SA(!__reference_constructs_from_temporary(C&, C&));
+SA(!__reference_constructs_from_temporary(C&, C&&));
+SA( __reference_constructs_from_temporary(const C&, C));
+SA(!__reference_constructs_from_temporary(const C&, C&));
+SA(!__reference_constructs_from_temporary(const C&, const C&));
+SA(!__reference_constructs_from_temporary(const C&, C&&));
+SA( __reference_constructs_from_temporary(C&&, C));
+SA(!__reference_constructs_from_temporary(C&&, C&));
+SA(!__reference_constructs_from_temporary(C&&, const C&));
+SA(!__reference_constructs_from_temporary(C&&, C&&));
+
+// These are all false ultimately because of CWG 2267, which we implement.
+SA(!__reference_constructs_from_temporary(A, D));
+SA(!__reference_constructs_from_temporary(A&, D));
+SA(!__reference_constructs_from_temporary(A&, D&));
+SA(!__reference_constructs_from_temporary(A&, const D&));
+SA(!__reference_constructs_from_temporary(A&, D&&));
+SA(!__reference_constructs_from_temporary(const A&, D));
+SA(!__reference_constructs_from_temporary(const A&, D&));
+SA(!__reference_constructs_from_temporary(const A&, const D&));
+SA(!__reference_constructs_from_temporary(const A&, D&&));
+SA(!__reference_constructs_from_temporary(A&&, D));
+SA(!__reference_constructs_from_temporary(A&&, D&));
+SA(!__reference_constructs_from_temporary(A&&, const D&));
+SA(!__reference_constructs_from_temporary(A&&, D&&));
+
+SA(!__reference_constructs_from_temporary(A, E));
+/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
+   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
+   so we're not binding the reference to a temporary, although there is
+   a temporary involved.  So the result is false in both copy- and direct-
+   init, albeit for different reasons!  */
+SA(!__reference_constructs_from_temporary(A&, E));
+// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
+SA(!__reference_constructs_from_temporary(A&, E&));
+SA(!__reference_constructs_from_temporary(A&, const E&));
+SA(!__reference_constructs_from_temporary(A&, E&&));
+// direct-init:
+// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
+SA(!__reference_constructs_from_temporary(const A&, E));
+SA(!__reference_constructs_from_temporary(const A&, E&));
+SA(!__reference_constructs_from_temporary(const A&, const E&));
+SA(!__reference_constructs_from_temporary(const A&, E&&));
+SA(!__reference_constructs_from_temporary(A&&, E));
+SA(!__reference_constructs_from_temporary(A&&, E&));
+SA(!__reference_constructs_from_temporary(A&&, const E&));
+SA(!__reference_constructs_from_temporary(A&&, E&&));
+
+SA(!__reference_constructs_from_temporary(A, F));
+// A& a1(F{}); and A& a2 = F{}; both invalid.
+SA(!__reference_constructs_from_temporary(A&, F));
+SA(!__reference_constructs_from_temporary(A&, F&));
+SA(!__reference_constructs_from_temporary(A&, const F&));
+SA(!__reference_constructs_from_temporary(A&, F&&));
+SA(!__reference_constructs_from_temporary(const A&, F));
+SA(!__reference_constructs_from_temporary(const A&, F&));
+SA(!__reference_constructs_from_temporary(const A&, const F&));
+SA(!__reference_constructs_from_temporary(const A&, F&&));
+SA(!__reference_constructs_from_temporary(A&&, F));
+SA(!__reference_constructs_from_temporary(A&&, F&));
+SA(!__reference_constructs_from_temporary(A&&, const F&));
+SA(!__reference_constructs_from_temporary(A&&, F&&));
+
+/* This is where _converts_ and _constructs_ will differ:
+   in direct-init we use G::operator int&& (no temporary),
+   but in copy-init we use G::operator int, where a temporary is created
+   to be bound to int&&.  */
+SA(!__reference_constructs_from_temporary(int&&, G));
+// Similar to the previous one.
+SA(!__reference_constructs_from_temporary(const int&, H));
+/* And here I've switched the explicit-ness.  In both copy- and direct-init
+   we call operator int&, so no temporary.  */
+SA(!__reference_constructs_from_temporary(int&&, G2));
+SA(!__reference_constructs_from_temporary(const int&, H2));
+
+SA(!__reference_constructs_from_temporary(const Base&, Der));
+
+// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
+SA(!__reference_constructs_from_temporary(int&&, id<int[3]>));
+
+// Arrays.
+SA(!__reference_constructs_from_temporary(int, int[]));
+SA(!__reference_constructs_from_temporary(int[], int[]));
+SA(!__reference_constructs_from_temporary(int&, int[]));
+SA(!__reference_constructs_from_temporary(int&&, int[]));
+SA(!__reference_constructs_from_temporary(const int&, int[]));
diff --git a/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
new file mode 100644
index 00000000000..90196c38742
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
@@ -0,0 +1,214 @@
+// P2255R2
+// PR c++/104477
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+struct A { A(); };
+struct B { operator A(); };
+struct C { explicit C(); };
+struct D { explicit operator A(); };
+struct E { explicit operator A&(); };
+struct F { explicit operator A&&(); };
+// Could use a class template with explicit(bool), but then this would need
+// C++20.
+struct G {
+  operator int();
+  explicit operator int&&();
+};
+struct G2 {
+  explicit operator int();
+  operator int&&();
+};
+struct H {
+  operator int();
+  explicit operator int&();
+};
+struct H2 {
+  explicit operator int();
+  operator int&();
+};
+
+struct Base { };
+struct Der : Base { };
+
+template<typename T, typename RT>
+struct morph {
+  mutable T val{};
+  operator RT() const { return static_cast<RT>(val); }
+};
+
+template<typename T> using id = T;
+
+// Built-in types.
+SA(!__reference_converts_from_temporary(int, int));
+SA(!__reference_converts_from_temporary(int&, void));
+SA(!__reference_converts_from_temporary(int&, const void));
+SA(!__reference_converts_from_temporary(int&, volatile void));
+SA(!__reference_converts_from_temporary(int&, const volatile void));
+SA(!__reference_converts_from_temporary(void, void));
+SA(!__reference_converts_from_temporary(void, int));
+SA(!__reference_converts_from_temporary(int&, int));
+SA(!__reference_converts_from_temporary(int&, int&));
+SA(!__reference_converts_from_temporary(int&, int&&));
+SA(!__reference_converts_from_temporary(int&, long));
+// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
+SA(!__reference_converts_from_temporary(int&, long&));
+SA(!__reference_converts_from_temporary(int&, long&&));
+SA( __reference_converts_from_temporary(const int&, int));
+SA(!__reference_converts_from_temporary(const int&, int&));
+SA(!__reference_converts_from_temporary(const int&, const int&));
+SA(!__reference_converts_from_temporary(const int&, int&&));
+SA( __reference_converts_from_temporary(const int&, long));
+SA( __reference_converts_from_temporary(const int&, long&));
+SA( __reference_converts_from_temporary(const int&, long&&));
+SA( __reference_converts_from_temporary(int&&, int));
+SA(!__reference_converts_from_temporary(int&&, int&));
+SA(!__reference_converts_from_temporary(int&&, int&&));
+SA( __reference_converts_from_temporary(int&&, long));
+SA( __reference_converts_from_temporary(int&&, long&));
+SA( __reference_converts_from_temporary(int&&, long&&));
+SA(!__reference_converts_from_temporary(unsigned int&, double));
+SA(!__reference_converts_from_temporary(volatile int&, int));
+SA(!__reference_converts_from_temporary(const volatile int&, int));
+SA(!__reference_converts_from_temporary(volatile int&, int&));
+SA(!__reference_converts_from_temporary(const volatile int&, int&));
+SA(!__reference_converts_from_temporary(volatile int&, int&&));
+SA(!__reference_converts_from_temporary(const volatile int&, int&&));
+
+// Classes.
+SA(!__reference_converts_from_temporary(A, A));
+// A& r(A{}); doesn't construct.
+SA(!__reference_converts_from_temporary(A&, A));
+SA(!__reference_converts_from_temporary(A&, A&));
+SA(!__reference_converts_from_temporary(A&, A&&));
+// Here we get const struct A & r = (const struct A &) &D.2414;
+SA( __reference_converts_from_temporary(const A&, A));
+SA(!__reference_converts_from_temporary(const A&, A&));
+SA(!__reference_converts_from_temporary(const A&, const A&));
+SA(!__reference_converts_from_temporary(const A&, A&&));
+// Here we get struct A & r = (struct A &) &D.2439;
+SA( __reference_converts_from_temporary(A&&, A));
+SA(!__reference_converts_from_temporary(A&&, A&));
+SA(!__reference_converts_from_temporary(A&&, const A&));
+SA(!__reference_converts_from_temporary(A&&, A&&));
+
+SA(!__reference_converts_from_temporary(A, B));
+SA(!__reference_converts_from_temporary(A&, B));
+SA(!__reference_converts_from_temporary(A&, B&));
+SA(!__reference_converts_from_temporary(A&, const B&));
+SA(!__reference_converts_from_temporary(A&, B&&));
+SA( __reference_converts_from_temporary(const A&, B));
+SA( __reference_converts_from_temporary(const A&, B&));
+// Doesn't construct, so it's false.
+SA(!__reference_converts_from_temporary(const A&, const B&));
+SA( __reference_converts_from_temporary(const A&, B&&));
+SA( __reference_converts_from_temporary(A&&, B));
+SA( __reference_converts_from_temporary(A&&, B&));
+SA(!__reference_converts_from_temporary(A&&, const B&));
+SA( __reference_converts_from_temporary(A&&, B&&));
+
+SA(!__reference_converts_from_temporary(const A&, C));
+SA(!__reference_converts_from_temporary(const A&, C&));
+
+// Doesn't construct, so it's false.
+SA(!__reference_converts_from_temporary(int&, morph<int, int>));
+// Here we get
+//   const int & r2 = D.2580 = morph<int, int>::operator int
+//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
+SA( __reference_converts_from_temporary(const int&, morph<int, int>));
+SA(!__reference_converts_from_temporary(int&, morph<int, int&>));
+SA(!__reference_converts_from_temporary(int&, morph<int, const int&>));
+SA(!__reference_converts_from_temporary(int&, morph<int, int&&>));
+SA( __reference_converts_from_temporary(const int&, morph<long, long&>));
+
+// These are like const C& c(cref); so the explicit ctor C isn't a problem
+// even in copy-init context.  const C& r = {}; would be a different story.
+SA(!__reference_converts_from_temporary(C, C));
+SA(!__reference_converts_from_temporary(C&, C));
+SA(!__reference_converts_from_temporary(C&, C&));
+SA(!__reference_converts_from_temporary(C&, C&&));
+SA( __reference_converts_from_temporary(const C&, C));
+SA(!__reference_converts_from_temporary(const C&, C&));
+SA(!__reference_converts_from_temporary(const C&, const C&));
+SA(!__reference_converts_from_temporary(const C&, C&&));
+SA( __reference_converts_from_temporary(C&&, C));
+SA(!__reference_converts_from_temporary(C&&, C&));
+SA(!__reference_converts_from_temporary(C&&, const C&));
+SA(!__reference_converts_from_temporary(C&&, C&&));
+
+// These are all false ultimately because of CWG 2267, which we implement.
+SA(!__reference_converts_from_temporary(A, D));
+SA(!__reference_converts_from_temporary(A&, D));
+SA(!__reference_converts_from_temporary(A&, D&));
+SA(!__reference_converts_from_temporary(A&, const D&));
+SA(!__reference_converts_from_temporary(A&, D&&));
+SA(!__reference_converts_from_temporary(const A&, D));
+SA(!__reference_converts_from_temporary(const A&, D&));
+SA(!__reference_converts_from_temporary(const A&, const D&));
+SA(!__reference_converts_from_temporary(const A&, D&&));
+SA(!__reference_converts_from_temporary(A&&, D));
+SA(!__reference_converts_from_temporary(A&&, D&));
+SA(!__reference_converts_from_temporary(A&&, const D&));
+SA(!__reference_converts_from_temporary(A&&, D&&));
+
+SA(!__reference_converts_from_temporary(A, E));
+/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
+   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
+   so we're not binding the reference to a temporary, although there is
+   a temporary involved.  So the result is false in both copy- and direct-
+   init, albeit for different reasons!  */
+SA(!__reference_converts_from_temporary(A&, E));
+// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
+SA(!__reference_converts_from_temporary(A&, E&));
+SA(!__reference_converts_from_temporary(A&, const E&));
+SA(!__reference_converts_from_temporary(A&, E&&));
+// direct-init:
+// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
+SA(!__reference_converts_from_temporary(const A&, E));
+SA(!__reference_converts_from_temporary(const A&, E&));
+SA(!__reference_converts_from_temporary(const A&, const E&));
+SA(!__reference_converts_from_temporary(const A&, E&&));
+SA(!__reference_converts_from_temporary(A&&, E));
+SA(!__reference_converts_from_temporary(A&&, E&));
+SA(!__reference_converts_from_temporary(A&&, const E&));
+SA(!__reference_converts_from_temporary(A&&, E&&));
+
+SA(!__reference_converts_from_temporary(A, F));
+// A& a1(F{}); and A& a2 = F{}; both invalid.
+SA(!__reference_converts_from_temporary(A&, F));
+SA(!__reference_converts_from_temporary(A&, F&));
+SA(!__reference_converts_from_temporary(A&, const F&));
+SA(!__reference_converts_from_temporary(A&, F&&));
+SA(!__reference_converts_from_temporary(const A&, F));
+SA(!__reference_converts_from_temporary(const A&, F&));
+SA(!__reference_converts_from_temporary(const A&, const F&));
+SA(!__reference_converts_from_temporary(const A&, F&&));
+SA(!__reference_converts_from_temporary(A&&, F));
+SA(!__reference_converts_from_temporary(A&&, F&));
+SA(!__reference_converts_from_temporary(A&&, const F&));
+SA(!__reference_converts_from_temporary(A&&, F&&));
+
+/* This is where _converts_ and _constructs_ will differ:
+   in direct-init we use G::operator int&& (no temporary),
+   but in copy-init we use G::operator int, where a temporary is created
+   to be bound to int&&.  */
+SA( __reference_converts_from_temporary(int&&, G));
+// Similar to the previous one.
+SA( __reference_converts_from_temporary(const int&, H));
+/* And here I've switched the explicit-ness.  In both copy- and direct-init
+   we call operator int&, so no temporary.  */
+SA(!__reference_converts_from_temporary(int&&, G2));
+SA(!__reference_converts_from_temporary(const int&, H2));
+
+SA(!__reference_converts_from_temporary(const Base&, Der));
+
+// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
+SA(!__reference_converts_from_temporary(int&&, id<int[3]>));
+
+// Arrays.
+SA(!__reference_converts_from_temporary(int, int[]));
+SA(!__reference_converts_from_temporary(int[], int[]));
+SA(!__reference_converts_from_temporary(int&, int[]));
+SA(!__reference_converts_from_temporary(int&&, int[]));
+SA(!__reference_converts_from_temporary(const int&, int[]));
diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index e5f58bc2e3f..b1a1deecf66 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -3505,6 +3505,45 @@ template<typename _Ret, typename _Fn, typename... _Args>
   template<typename _Tp>
     inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
 
+#define __cpp_lib_reference_from_temporary 202202L
+
+  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
+  /// direct-initialization, and a temporary object would be bound to
+  /// the reference, false otherwise.
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    struct reference_constructs_from_temporary
+    : public bool_constant<__reference_constructs_from_temporary(_Tp, _Up)>
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
+		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
+	"template argument must be a complete class or an unbounded array");
+    };
+
+  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
+  /// copy-initialization, and a temporary object would be bound to
+  /// the reference, false otherwise.
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    struct reference_converts_from_temporary
+    : public bool_constant<__reference_converts_from_temporary(_Tp, _Up)>
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
+		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
+	"template argument must be a complete class or an unbounded array");
+    };
+
+  /// @ingroup variable_templates
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    inline constexpr bool reference_constructs_from_temporary_v
+      = reference_constructs_from_temporary<_Tp, _Up>::value;
+
+  /// @ingroup variable_templates
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    inline constexpr bool reference_converts_from_temporary_v
+      = reference_converts_from_temporary<_Tp, _Up>::value;
 #endif // C++23
 
 #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
index 22280e1a349..5edca2f3007 100644
--- a/libstdc++-v3/include/std/version
+++ b/libstdc++-v3/include/std/version
@@ -300,10 +300,11 @@
 #endif
 
 #if __cplusplus > 202002L
-// c++2b
+// c++23
 #define __cpp_lib_byteswap 202110L
 #define __cpp_lib_constexpr_typeinfo 202106L
 #define __cpp_lib_is_scoped_enum 202011L
+#define __cpp_lib_reference_from_temporary 202202L
 
 #if _GLIBCXX_HOSTED
 #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
@@ -335,7 +336,7 @@
 #define __cpp_lib_to_underlying 202102L
 #define __cpp_lib_unreachable 202202L
 #endif
-#endif // C++2b
+#endif // C++23
 #endif // C++20
 #endif // C++17
 #endif // C++14
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
new file mode 100644
index 00000000000..2f62e54d46d
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
@@ -0,0 +1,110 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <type_traits>
+#include <testsuite_tr1.h>
+
+#ifndef __cpp_lib_reference_from_temporary
+# error "Feature test macro for reference_from_temporary is missing in <version>"
+#elif __cpp_lib_reference_from_temporary < 202202L
+# error "Feature test macro for reference_from_temporary has wrong value in <version>"
+#endif
+
+void test01()
+{
+  using std::reference_constructs_from_temporary;
+  using std::reference_converts_from_temporary;
+  using namespace __gnu_test;
+
+  struct A { A(); };
+
+  struct B {
+    operator int();
+    explicit operator int&&();
+  };
+
+  struct C {
+    operator int();
+    explicit operator int&();
+  };
+
+  static_assert(test_property<reference_constructs_from_temporary, int, int>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, const volatile void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, void, void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long&&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long&&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const A&, A>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const A&, A&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, A&&, A>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int[]>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int[]>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int[]>(false), "");
+
+  static_assert(test_property<reference_converts_from_temporary, int, int>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, const volatile void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, void, void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long&&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long&&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const A&, A>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const A&, A&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, A&&, A>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int[]>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int[]>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int[]>(false), "");
+
+  static_assert(test_property<reference_constructs_from_temporary, int&&, B>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, C>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, B>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, C>(true), "");
+}
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
new file mode 100644
index 00000000000..65770754299
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <type_traits>
+#include <string>
+
+void test01()
+{
+  static_assert(std::reference_converts_from_temporary_v<const std::string&, const char*>);
+  static_assert(std::reference_constructs_from_temporary_v<const std::string&, const char*>);
+}
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
new file mode 100644
index 00000000000..f56e7c0dabc
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <version>
+
+#ifndef __cpp_lib_reference_from_temporary
+# error "Feature test macro for reference_from_temporary is missing in <version>"
+#elif __cpp_lib_reference_from_temporary < 202202L
+# error "Feature test macro for reference_from_temporary has wrong value in <version>"
+#endif
diff --git a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
index 9b3957f7d47..2b03ad7067d 100644
--- a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
+++ b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
@@ -346,3 +346,17 @@ static_assert(disjunction_v<false_type, false_type,
               true_type>, "");
 static_assert(!disjunction_v<false_type, false_type,
               false_type>, "");
+#if __cpp_lib_reference_from_temporary >= 202202L
+static_assert(std::reference_converts_from_temporary_v<int&&, int>
+	      && std::reference_converts_from_temporary_v<const int&, int>
+	      && !std::reference_converts_from_temporary_v<int&&, int&&>
+	      && !std::reference_converts_from_temporary_v<const int&, int&&>
+	      && std::reference_converts_from_temporary_v<int&&, long&&>
+	      && std::reference_converts_from_temporary_v<int&&, long>, "");
+static_assert(std::reference_constructs_from_temporary_v<int&&, int>
+	      && std::reference_constructs_from_temporary_v<const int&, int>
+	      && !std::reference_constructs_from_temporary_v<int&&, int&&>
+	      && !std::reference_constructs_from_temporary_v<const int&, int&&>
+	      && std::reference_constructs_from_temporary_v<int&&, long&&>
+	      && std::reference_constructs_from_temporary_v<int&&, long>, "");
+#endif

base-commit: 29f40a8047fa9b6ccde2174ca126b78a535e1a61
-- 
2.36.1


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-12 20:15   ` Jason Merrill
@ 2022-07-14 17:43     ` Marek Polacek
  2022-07-15  3:48       ` Jason Merrill
  0 siblings, 1 reply; 15+ messages in thread
From: Marek Polacek @ 2022-07-14 17:43 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches, libstdc++, Jonathan Wakely

On Tue, Jul 12, 2022 at 04:15:00PM -0400, Jason Merrill wrote:
> On 7/12/22 16:10, Jason Merrill wrote:
> > On 7/8/22 13:41, Marek Polacek wrote:
> > > This patch implements C++23 P2255R2, which adds two new type traits to
> > > detect reference binding to a temporary.  They can be used to detect code
> > > like
> > > 
> > >    std::tuple<const std::string&> t("meow");
> > > 
> > > which is incorrect because it always creates a dangling reference,
> > > because
> > > the std::string temporary is created inside the selected constructor of
> > > std::tuple, and not outside it.
> > > 
> > > There are two new compiler builtins,
> > > __reference_constructs_from_temporary
> > > and __reference_converts_from_temporary.  The former is used to simulate
> > > direct- and the latter copy-initialization context.  But I had a
> > > hard time
> > > finding a test where there's actually a difference.  Under DR 2267, both
> > > of these are invalid:
> > > 
> > >    struct A { } a;
> > >    struct B { explicit B(const A&); };
> > >    const B &b1{a};
> > >    const B &b2(a);
> > > 
> > > so I had to peruse [over.match.ref], and eventually realized that the
> > > difference can be seen here:
> > > 
> > >    struct G {
> > >      operator int(); // #1
> > >      explicit operator int&&(); // #2
> > >    };
> > > 
> > > int&& r1(G{}); // use #2 (no temporary)
> > > int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)
> > > 
> > > The implementation itself was rather straightforward because we already
> > > have conv_binds_ref_to_prvalue.  The main function here is
> > > reference_from_temporary.  The renaming to ref_conv_binds_to_temporary_p
> > > is because previously the function didn't distinguish between an invalid
> > > conversion and one that binds to a prvalue.
> > > 
> > > The patch also adds the relevant class and variable templates to
> > > <type_traits>.
> > > 
> > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > 
> > >     PR c++/104477
> > > 
> > > gcc/c-family/ChangeLog:
> > > 
> > >     * c-common.cc (c_common_reswords): Add
> > >     __reference_constructs_from_temporary and
> > >     __reference_converts_from_temporary.
> > >     * c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> > >     RID_REF_CONVERTS_FROM_TEMPORARY.
> > > 
> > > gcc/cp/ChangeLog:
> > > 
> > >     * call.cc (ref_conv_binds_directly_p): Rename to ...
> > >     (ref_conv_binds_to_temporary_p): ... this.  Add a new bool
> > >     parameter.  Return true only if the conversion is valid and
> > >     conv_binds_ref_to_prvalue returns true.
> > >     * constraint.cc (diagnose_trait_expr): Handle
> > >     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > >     * cp-tree.h (enum cp_trait_kind): Add
> > > CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
> > >     and CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > >     (ref_conv_binds_directly_p): Rename to ...
> > >     (ref_conv_binds_to_temporary_p): ... this.
> > >     (reference_from_temporary): Declare.
> > >     * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
> > >     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > >     * method.cc (reference_from_temporary): New.
> > >     * parser.cc (cp_parser_primary_expression): Handle
> > >     RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > RID_REF_CONVERTS_FROM_TEMPORARY.
> > >     (cp_parser_trait_expr): Likewise.
> > >     (warn_for_range_copy): Adjust to call ref_conv_binds_to_temporary_p.
> > >     * semantics.cc (trait_expr_value): Handle
> > >     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > >     (finish_trait_expr): Likewise.
> > > 
> > > libstdc++-v3/ChangeLog:
> > > 
> > >     * include/std/type_traits (reference_constructs_from_temporary,
> > >     reference_converts_from_temporary): New class templates.
> > >     (reference_constructs_from_temporary_v,
> > >     reference_converts_from_temporary_v): New variable templates.
> > >     (__cpp_lib_reference_from_temporary): Define for C++23.
> > >     * include/std/version (__cpp_lib_reference_from_temporary):
> > > Define for
> > >     C++23.
> > >     * testsuite/20_util/variable_templates_for_traits.cc: Test
> > >     reference_constructs_from_temporary_v and
> > >     reference_converts_from_temporary_v.
> > >     * testsuite/20_util/reference_from_temporary/value.cc: New test.
> > >     * testsuite/20_util/reference_from_temporary/value2.cc: New test.
> > >     * testsuite/20_util/reference_from_temporary/version.cc: New test.
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > >     * g++.dg/ext/reference_constructs_from_temporary1.C: New test.
> > >     * g++.dg/ext/reference_converts_from_temporary1.C: New test.
> > > ---
> > >   gcc/c-family/c-common.cc                      |   4 +
> > >   gcc/c-family/c-common.h                       |   2 +
> > >   gcc/cp/call.cc                                |  14 +-
> > >   gcc/cp/constraint.cc                          |   8 +
> > >   gcc/cp/cp-tree.h                              |   7 +-
> > >   gcc/cp/cxx-pretty-print.cc                    |   6 +
> > >   gcc/cp/method.cc                              |  28 +++
> > >   gcc/cp/parser.cc                              |  14 +-
> > >   gcc/cp/semantics.cc                           |   8 +
> > >   .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
> > >   .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
> > >   libstdc++-v3/include/std/type_traits          |  39 ++++
> > >   libstdc++-v3/include/std/version              |   5 +-
> > >   .../20_util/reference_from_temporary/value.cc | 110 +++++++++
> > >   .../reference_from_temporary/value2.cc        |  28 +++
> > >   .../reference_from_temporary/version.cc       |  27 +++
> > >   .../20_util/variable_templates_for_traits.cc  |  14 ++
> > >   17 files changed, 730 insertions(+), 12 deletions(-)
> > >   create mode 100644
> > > gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> > >   create mode 100644
> > > gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> > >   create mode 100644
> > > libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> > >   create mode 100644
> > > libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> > >   create mode 100644
> > > libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> > > 
> > > diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> > > index 1b8e73f7bc5..655c3aefee6 100644
> > > --- a/gcc/c-family/c-common.cc
> > > +++ b/gcc/c-family/c-common.cc
> > > @@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
> > >     { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
> > >     { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
> > >     { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE,
> > > D_CXXONLY },
> > > +  { "__reference_constructs_from_temporary",
> > > RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > +                    D_CXXONLY },
> > > +  { "__reference_converts_from_temporary",
> > > RID_REF_CONVERTS_FROM_TEMPORARY,
> > > +                    D_CXXONLY },
> > >     /* C++ transactional memory.  */
> > >     { "synchronized",    RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
> > > diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> > > index c0900848965..f9064393b4e 100644
> > > --- a/gcc/c-family/c-common.h
> > > +++ b/gcc/c-family/c-common.h
> > > @@ -184,6 +184,8 @@ enum rid
> > >     RID_IS_UNION,                RID_UNDERLYING_TYPE,
> > >     RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
> > >     RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
> > > +  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > +  RID_REF_CONVERTS_FROM_TEMPORARY,
> > >     /* C++11 */
> > >     RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR,
> > > RID_STATIC_ASSERT,
> > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> > > index fc98552fda2..1ba209f61f1 100644
> > > --- a/gcc/cp/call.cc
> > > +++ b/gcc/cp/call.cc
> > > @@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
> > >     return conv_is_prvalue (next_conversion (c));
> > >   }
> > > -/* True iff converting EXPR to a reference type TYPE does not involve
> > > -   creating a temporary.  */
> > > +/* True iff converting EXPR to a reference type TYPE binds the
> > > reference to
> > > +   a temporary.  DIRECT_INIT_P says whether the conversion should
> > > be done
> > > +   in direct- or copy-initialization context.  */
> > >   bool
> > > -ref_conv_binds_directly_p (tree type, tree expr)
> > > +ref_conv_binds_to_temporary_p (tree type, tree expr,
> > > +                   bool direct_init_p /*= false*/)
> > >   {
> > >     gcc_assert (TYPE_REF_P (type));
> > >     /* Get the high-water mark for the CONVERSION_OBSTACK.  */
> > >     void *p = conversion_obstack_alloc (0);
> > > +  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
> > >     conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
> > > -                      /*c_cast_p=*/false,
> > > -                      LOOKUP_IMPLICIT, tf_none);
> > > -  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
> > > +                      /*c_cast_p=*/false, flags, tf_none);
> > > +  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue (conv);
> > >     /* Free all the conversions we allocated.  */
> > >     obstack_free (&conversion_obstack, p);
> > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > index 591155cee22..648cc9d176d 100644
> > > --- a/gcc/cp/constraint.cc
> > > +++ b/gcc/cp/constraint.cc
> > > @@ -3687,6 +3687,14 @@ diagnose_trait_expr (tree expr, tree args)
> > >       case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
> > >         inform (loc, "  %qT does not have unique object
> > > representations", t1);
> > >         break;
> > > +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > +      inform (loc, "  %qT is not a reference that binds to a temporary "
> > > +          "object of type %qT (direct-initialization)", t1, t2);
> > > +      break;
> > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > +      inform (loc, "  %qT is not a reference that binds to a temporary "
> > > +          "object of type %qT (copy-initialization)", t1, t2);
> > > +      break;
> > >       case CPTK_BASES:
> > >       case CPTK_DIRECT_BASES:
> > >       case CPTK_UNDERLYING_TYPE:
> > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > > index 2fde4f83b41..c3bed31e455 100644
> > > --- a/gcc/cp/cp-tree.h
> > > +++ b/gcc/cp/cp-tree.h
> > > @@ -1397,7 +1397,9 @@ enum cp_trait_kind
> > >     CPTK_IS_ASSIGNABLE,
> > >     CPTK_IS_CONSTRUCTIBLE,
> > >     CPTK_IS_NOTHROW_ASSIGNABLE,
> > > -  CPTK_IS_NOTHROW_CONSTRUCTIBLE
> > > +  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
> > > +  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > +  CPTK_REF_CONVERTS_FROM_TEMPORARY
> > >   };
> > >   /* The types that we are processing.  */
> > > @@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p
> > > (const_tree);
> > >   extern tree type_decays_to            (tree);
> > >   extern tree extract_call_expr            (tree);
> > >   extern tree build_trivial_dtor_call        (tree, bool = false);
> > > -extern bool ref_conv_binds_directly_p        (tree, tree);
> > > +extern bool ref_conv_binds_to_temporary_p    (tree, tree, bool = false);
> > >   extern tree build_user_type_conversion        (tree, tree, int,
> > >                            tsubst_flags_t);
> > >   extern tree build_new_function_call        (tree, vec<tree, va_gc> **,
> > > @@ -7105,6 +7107,7 @@ extern tree forward_parm            (tree);
> > >   extern bool is_trivially_xible            (enum tree_code, tree, tree);
> > >   extern bool is_nothrow_xible            (enum tree_code, tree, tree);
> > >   extern bool is_xible                (enum tree_code, tree, tree);
> > > +extern bool reference_from_temporary        (tree, tree, bool);
> > >   extern tree get_defaulted_eh_spec        (tree, tsubst_flags_t =
> > > tf_warning_or_error);
> > >   extern bool maybe_explain_implicit_delete    (tree);
> > >   extern void explain_implicit_non_constexpr    (tree);
> > > diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> > > index 7e4db2e413b..44590830a61 100644
> > > --- a/gcc/cp/cxx-pretty-print.cc
> > > +++ b/gcc/cp/cxx-pretty-print.cc
> > > @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer
> > > *pp, tree t)
> > >       case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
> > >         pp_cxx_ws_string (pp, "__is_nothrow_constructible");
> > >         break;
> > > +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > +      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
> > > +      break;
> > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > +      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
> > > +      break;
> > >       default:
> > >         gcc_unreachable ();
> > > diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> > > index 0dffd648b0b..dd9715b6725 100644
> > > --- a/gcc/cp/method.cc
> > > +++ b/gcc/cp/method.cc
> > > @@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to, tree from)
> > >     return !!expr;
> > >   }
> > > +/* Return true iff conjunction_v<is_reference<T>,
> > > is_constructible<T, U>> is
> > > +   true, and the initialization
> > > +     T t(VAL<U>); // DIRECT_INIT_P
> > > +   or
> > > +     T t = VAL<U>; // !DIRECT_INIT_P
> > > +   binds t to a temporary object whose lifetime is extended.
> > > +   VAL<T> is defined in [meta.unary.prop]:
> > > +   -- If T is a reference or function type, VAL<T> is an expression
> > > with the
> > > +   same type and value category as declval<T>().
> > > +   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
> > > +
> > > +bool
> > > +reference_from_temporary (tree to, tree from, bool direct_init_p)
> > > +{
> > > +  /* Check is_reference<T>.  */
> > > +  if (!TYPE_REF_P (to))
> > > +    return false;
> > > +  /* Check is_constructible<T, U>.
> > > +     ??? This check doesn't seem to be necessary; if T isn't
> > > constructible
> > > +     from U, we won't be able to create a conversion.  */
> > > +  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
> > > +    return false;
> > 
> > I agree with the comment, did you try leaving this out?  If it stays I'd
> > think it needs to consider direct_init_p.
> > 
> > > +  tree val = build_stub_object (from);
> > > +  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
> > > +    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) :
> > > rvalue (val);
> > > +  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
> > > +}
> > > +
> > >   /* Categorize various special_function_kinds.  */
> > >   #define SFK_CTOR_P(sfk) \
> > >     ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
> > > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> > > index bf9ea3685f8..edee94bda13 100644
> > > --- a/gcc/cp/parser.cc
> > > +++ b/gcc/cp/parser.cc
> > > @@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
> > >       case RID_IS_CONSTRUCTIBLE:
> > >       case RID_IS_NOTHROW_ASSIGNABLE:
> > >       case RID_IS_NOTHROW_CONSTRUCTIBLE:
> > > +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> > >         return cp_parser_trait_expr (parser, token->keyword);
> > >       // C++ concepts
> > > @@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser,
> > > enum rid keyword)
> > >         kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
> > >         variadic = true;
> > >         break;
> > > +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > +      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
> > > +      binary = true;
> > > +      break;
> > > +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> > > +      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
> > > +      binary = true;
> > > +      break;
> > >       default:
> > >         gcc_unreachable ();
> > >       }
> > > @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
> > >     if (TYPE_REF_P (type))
> > >       {
> > > -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
> > > +      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type,
> > > expr))
> > >       {
> > >         auto_diagnostic_group d;
> > >         if (warning_at (loc, OPT_Wrange_loop_construct,
> > > @@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
> > >     tree rtype = cp_build_reference_type (type, /*rval*/false);
> > >     /* If we could initialize the reference directly, it wouldn't
> > > involve any
> > >        copies.  */
> > > -  if (!ref_conv_binds_directly_p (rtype, expr))
> > > +  if (ref_conv_binds_to_temporary_p (rtype, expr))
> > >       return;
> > 
> > I think this case wants the old handling of invalid conversions you
> > mentioned in your intro; we don't want to suggest changing to a
> > reference if that's ill-formed.
> > 
> > In passing we might change the comment to "If we can initialize a
> > reference directly, suggest that to avoid the copy." and move it above
> > the rtype declaration.
> 
> Hmm, and I suspect we get false positives when expr is a prvalue, so
> initializing a non-reference variable of the same cv-unqualified type
> involves no extra copy?

I couldn't provoke a false positive here.  Note that ref_conv_binds_to_temporary_p
always gets a reference so copy elision isn't applicable here.

Marek


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-14 17:43     ` Marek Polacek
@ 2022-07-15  3:48       ` Jason Merrill
  2022-07-15 15:32         ` Marek Polacek
  0 siblings, 1 reply; 15+ messages in thread
From: Jason Merrill @ 2022-07-15  3:48 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches, libstdc++, Jonathan Wakely

On 7/14/22 13:43, Marek Polacek wrote:
> On Tue, Jul 12, 2022 at 04:15:00PM -0400, Jason Merrill wrote:
>> On 7/12/22 16:10, Jason Merrill wrote:
>>> On 7/8/22 13:41, Marek Polacek wrote:
>>>> This patch implements C++23 P2255R2, which adds two new type traits to
>>>> detect reference binding to a temporary.  They can be used to detect code
>>>> like
>>>>
>>>>     std::tuple<const std::string&> t("meow");
>>>>
>>>> which is incorrect because it always creates a dangling reference,
>>>> because
>>>> the std::string temporary is created inside the selected constructor of
>>>> std::tuple, and not outside it.
>>>>
>>>> There are two new compiler builtins,
>>>> __reference_constructs_from_temporary
>>>> and __reference_converts_from_temporary.  The former is used to simulate
>>>> direct- and the latter copy-initialization context.  But I had a
>>>> hard time
>>>> finding a test where there's actually a difference.  Under DR 2267, both
>>>> of these are invalid:
>>>>
>>>>     struct A { } a;
>>>>     struct B { explicit B(const A&); };
>>>>     const B &b1{a};
>>>>     const B &b2(a);
>>>>
>>>> so I had to peruse [over.match.ref], and eventually realized that the
>>>> difference can be seen here:
>>>>
>>>>     struct G {
>>>>       operator int(); // #1
>>>>       explicit operator int&&(); // #2
>>>>     };
>>>>
>>>> int&& r1(G{}); // use #2 (no temporary)
>>>> int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)
>>>>
>>>> The implementation itself was rather straightforward because we already
>>>> have conv_binds_ref_to_prvalue.  The main function here is
>>>> reference_from_temporary.  The renaming to ref_conv_binds_to_temporary_p
>>>> is because previously the function didn't distinguish between an invalid
>>>> conversion and one that binds to a prvalue.
>>>>
>>>> The patch also adds the relevant class and variable templates to
>>>> <type_traits>.
>>>>
>>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>>>
>>>>      PR c++/104477
>>>>
>>>> gcc/c-family/ChangeLog:
>>>>
>>>>      * c-common.cc (c_common_reswords): Add
>>>>      __reference_constructs_from_temporary and
>>>>      __reference_converts_from_temporary.
>>>>      * c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
>>>>      RID_REF_CONVERTS_FROM_TEMPORARY.
>>>>
>>>> gcc/cp/ChangeLog:
>>>>
>>>>      * call.cc (ref_conv_binds_directly_p): Rename to ...
>>>>      (ref_conv_binds_to_temporary_p): ... this.  Add a new bool
>>>>      parameter.  Return true only if the conversion is valid and
>>>>      conv_binds_ref_to_prvalue returns true.
>>>>      * constraint.cc (diagnose_trait_expr): Handle
>>>>      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
>>>> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>>>>      * cp-tree.h (enum cp_trait_kind): Add
>>>> CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
>>>>      and CPTK_REF_CONVERTS_FROM_TEMPORARY.
>>>>      (ref_conv_binds_directly_p): Rename to ...
>>>>      (ref_conv_binds_to_temporary_p): ... this.
>>>>      (reference_from_temporary): Declare.
>>>>      * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
>>>>      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
>>>> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>>>>      * method.cc (reference_from_temporary): New.
>>>>      * parser.cc (cp_parser_primary_expression): Handle
>>>>      RID_REF_CONSTRUCTS_FROM_TEMPORARY and
>>>> RID_REF_CONVERTS_FROM_TEMPORARY.
>>>>      (cp_parser_trait_expr): Likewise.
>>>>      (warn_for_range_copy): Adjust to call ref_conv_binds_to_temporary_p.
>>>>      * semantics.cc (trait_expr_value): Handle
>>>>      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
>>>> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>>>>      (finish_trait_expr): Likewise.
>>>>
>>>> libstdc++-v3/ChangeLog:
>>>>
>>>>      * include/std/type_traits (reference_constructs_from_temporary,
>>>>      reference_converts_from_temporary): New class templates.
>>>>      (reference_constructs_from_temporary_v,
>>>>      reference_converts_from_temporary_v): New variable templates.
>>>>      (__cpp_lib_reference_from_temporary): Define for C++23.
>>>>      * include/std/version (__cpp_lib_reference_from_temporary):
>>>> Define for
>>>>      C++23.
>>>>      * testsuite/20_util/variable_templates_for_traits.cc: Test
>>>>      reference_constructs_from_temporary_v and
>>>>      reference_converts_from_temporary_v.
>>>>      * testsuite/20_util/reference_from_temporary/value.cc: New test.
>>>>      * testsuite/20_util/reference_from_temporary/value2.cc: New test.
>>>>      * testsuite/20_util/reference_from_temporary/version.cc: New test.
>>>>
>>>> gcc/testsuite/ChangeLog:
>>>>
>>>>      * g++.dg/ext/reference_constructs_from_temporary1.C: New test.
>>>>      * g++.dg/ext/reference_converts_from_temporary1.C: New test.
>>>> ---
>>>>    gcc/c-family/c-common.cc                      |   4 +
>>>>    gcc/c-family/c-common.h                       |   2 +
>>>>    gcc/cp/call.cc                                |  14 +-
>>>>    gcc/cp/constraint.cc                          |   8 +
>>>>    gcc/cp/cp-tree.h                              |   7 +-
>>>>    gcc/cp/cxx-pretty-print.cc                    |   6 +
>>>>    gcc/cp/method.cc                              |  28 +++
>>>>    gcc/cp/parser.cc                              |  14 +-
>>>>    gcc/cp/semantics.cc                           |   8 +
>>>>    .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
>>>>    .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
>>>>    libstdc++-v3/include/std/type_traits          |  39 ++++
>>>>    libstdc++-v3/include/std/version              |   5 +-
>>>>    .../20_util/reference_from_temporary/value.cc | 110 +++++++++
>>>>    .../reference_from_temporary/value2.cc        |  28 +++
>>>>    .../reference_from_temporary/version.cc       |  27 +++
>>>>    .../20_util/variable_templates_for_traits.cc  |  14 ++
>>>>    17 files changed, 730 insertions(+), 12 deletions(-)
>>>>    create mode 100644
>>>> gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
>>>>    create mode 100644
>>>> gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
>>>>    create mode 100644
>>>> libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
>>>>    create mode 100644
>>>> libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
>>>>    create mode 100644
>>>> libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
>>>>
>>>> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
>>>> index 1b8e73f7bc5..655c3aefee6 100644
>>>> --- a/gcc/c-family/c-common.cc
>>>> +++ b/gcc/c-family/c-common.cc
>>>> @@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
>>>>      { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
>>>>      { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
>>>>      { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE,
>>>> D_CXXONLY },
>>>> +  { "__reference_constructs_from_temporary",
>>>> RID_REF_CONSTRUCTS_FROM_TEMPORARY,
>>>> +                    D_CXXONLY },
>>>> +  { "__reference_converts_from_temporary",
>>>> RID_REF_CONVERTS_FROM_TEMPORARY,
>>>> +                    D_CXXONLY },
>>>>      /* C++ transactional memory.  */
>>>>      { "synchronized",    RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
>>>> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
>>>> index c0900848965..f9064393b4e 100644
>>>> --- a/gcc/c-family/c-common.h
>>>> +++ b/gcc/c-family/c-common.h
>>>> @@ -184,6 +184,8 @@ enum rid
>>>>      RID_IS_UNION,                RID_UNDERLYING_TYPE,
>>>>      RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
>>>>      RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
>>>> +  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
>>>> +  RID_REF_CONVERTS_FROM_TEMPORARY,
>>>>      /* C++11 */
>>>>      RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR,
>>>> RID_STATIC_ASSERT,
>>>> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
>>>> index fc98552fda2..1ba209f61f1 100644
>>>> --- a/gcc/cp/call.cc
>>>> +++ b/gcc/cp/call.cc
>>>> @@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
>>>>      return conv_is_prvalue (next_conversion (c));
>>>>    }
>>>> -/* True iff converting EXPR to a reference type TYPE does not involve
>>>> -   creating a temporary.  */
>>>> +/* True iff converting EXPR to a reference type TYPE binds the
>>>> reference to
>>>> +   a temporary.  DIRECT_INIT_P says whether the conversion should
>>>> be done
>>>> +   in direct- or copy-initialization context.  */
>>>>    bool
>>>> -ref_conv_binds_directly_p (tree type, tree expr)
>>>> +ref_conv_binds_to_temporary_p (tree type, tree expr,
>>>> +                   bool direct_init_p /*= false*/)
>>>>    {
>>>>      gcc_assert (TYPE_REF_P (type));
>>>>      /* Get the high-water mark for the CONVERSION_OBSTACK.  */
>>>>      void *p = conversion_obstack_alloc (0);
>>>> +  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
>>>>      conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
>>>> -                      /*c_cast_p=*/false,
>>>> -                      LOOKUP_IMPLICIT, tf_none);
>>>> -  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
>>>> +                      /*c_cast_p=*/false, flags, tf_none);
>>>> +  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue (conv);
>>>>      /* Free all the conversions we allocated.  */
>>>>      obstack_free (&conversion_obstack, p);
>>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>>> index 591155cee22..648cc9d176d 100644
>>>> --- a/gcc/cp/constraint.cc
>>>> +++ b/gcc/cp/constraint.cc
>>>> @@ -3687,6 +3687,14 @@ diagnose_trait_expr (tree expr, tree args)
>>>>        case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
>>>>          inform (loc, "  %qT does not have unique object
>>>> representations", t1);
>>>>          break;
>>>> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
>>>> +      inform (loc, "  %qT is not a reference that binds to a temporary "
>>>> +          "object of type %qT (direct-initialization)", t1, t2);
>>>> +      break;
>>>> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
>>>> +      inform (loc, "  %qT is not a reference that binds to a temporary "
>>>> +          "object of type %qT (copy-initialization)", t1, t2);
>>>> +      break;
>>>>        case CPTK_BASES:
>>>>        case CPTK_DIRECT_BASES:
>>>>        case CPTK_UNDERLYING_TYPE:
>>>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>>>> index 2fde4f83b41..c3bed31e455 100644
>>>> --- a/gcc/cp/cp-tree.h
>>>> +++ b/gcc/cp/cp-tree.h
>>>> @@ -1397,7 +1397,9 @@ enum cp_trait_kind
>>>>      CPTK_IS_ASSIGNABLE,
>>>>      CPTK_IS_CONSTRUCTIBLE,
>>>>      CPTK_IS_NOTHROW_ASSIGNABLE,
>>>> -  CPTK_IS_NOTHROW_CONSTRUCTIBLE
>>>> +  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
>>>> +  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
>>>> +  CPTK_REF_CONVERTS_FROM_TEMPORARY
>>>>    };
>>>>    /* The types that we are processing.  */
>>>> @@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p
>>>> (const_tree);
>>>>    extern tree type_decays_to            (tree);
>>>>    extern tree extract_call_expr            (tree);
>>>>    extern tree build_trivial_dtor_call        (tree, bool = false);
>>>> -extern bool ref_conv_binds_directly_p        (tree, tree);
>>>> +extern bool ref_conv_binds_to_temporary_p    (tree, tree, bool = false);
>>>>    extern tree build_user_type_conversion        (tree, tree, int,
>>>>                             tsubst_flags_t);
>>>>    extern tree build_new_function_call        (tree, vec<tree, va_gc> **,
>>>> @@ -7105,6 +7107,7 @@ extern tree forward_parm            (tree);
>>>>    extern bool is_trivially_xible            (enum tree_code, tree, tree);
>>>>    extern bool is_nothrow_xible            (enum tree_code, tree, tree);
>>>>    extern bool is_xible                (enum tree_code, tree, tree);
>>>> +extern bool reference_from_temporary        (tree, tree, bool);
>>>>    extern tree get_defaulted_eh_spec        (tree, tsubst_flags_t =
>>>> tf_warning_or_error);
>>>>    extern bool maybe_explain_implicit_delete    (tree);
>>>>    extern void explain_implicit_non_constexpr    (tree);
>>>> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
>>>> index 7e4db2e413b..44590830a61 100644
>>>> --- a/gcc/cp/cxx-pretty-print.cc
>>>> +++ b/gcc/cp/cxx-pretty-print.cc
>>>> @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer
>>>> *pp, tree t)
>>>>        case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
>>>>          pp_cxx_ws_string (pp, "__is_nothrow_constructible");
>>>>          break;
>>>> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
>>>> +      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
>>>> +      break;
>>>> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
>>>> +      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
>>>> +      break;
>>>>        default:
>>>>          gcc_unreachable ();
>>>> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
>>>> index 0dffd648b0b..dd9715b6725 100644
>>>> --- a/gcc/cp/method.cc
>>>> +++ b/gcc/cp/method.cc
>>>> @@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to, tree from)
>>>>      return !!expr;
>>>>    }
>>>> +/* Return true iff conjunction_v<is_reference<T>,
>>>> is_constructible<T, U>> is
>>>> +   true, and the initialization
>>>> +     T t(VAL<U>); // DIRECT_INIT_P
>>>> +   or
>>>> +     T t = VAL<U>; // !DIRECT_INIT_P
>>>> +   binds t to a temporary object whose lifetime is extended.
>>>> +   VAL<T> is defined in [meta.unary.prop]:
>>>> +   -- If T is a reference or function type, VAL<T> is an expression
>>>> with the
>>>> +   same type and value category as declval<T>().
>>>> +   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
>>>> +
>>>> +bool
>>>> +reference_from_temporary (tree to, tree from, bool direct_init_p)
>>>> +{
>>>> +  /* Check is_reference<T>.  */
>>>> +  if (!TYPE_REF_P (to))
>>>> +    return false;
>>>> +  /* Check is_constructible<T, U>.
>>>> +     ??? This check doesn't seem to be necessary; if T isn't
>>>> constructible
>>>> +     from U, we won't be able to create a conversion.  */
>>>> +  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
>>>> +    return false;
>>>
>>> I agree with the comment, did you try leaving this out?  If it stays I'd
>>> think it needs to consider direct_init_p.
>>>
>>>> +  tree val = build_stub_object (from);
>>>> +  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
>>>> +    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) :
>>>> rvalue (val);
>>>> +  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
>>>> +}
>>>> +
>>>>    /* Categorize various special_function_kinds.  */
>>>>    #define SFK_CTOR_P(sfk) \
>>>>      ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
>>>> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
>>>> index bf9ea3685f8..edee94bda13 100644
>>>> --- a/gcc/cp/parser.cc
>>>> +++ b/gcc/cp/parser.cc
>>>> @@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
>>>>        case RID_IS_CONSTRUCTIBLE:
>>>>        case RID_IS_NOTHROW_ASSIGNABLE:
>>>>        case RID_IS_NOTHROW_CONSTRUCTIBLE:
>>>> +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
>>>> +    case RID_REF_CONVERTS_FROM_TEMPORARY:
>>>>          return cp_parser_trait_expr (parser, token->keyword);
>>>>        // C++ concepts
>>>> @@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser,
>>>> enum rid keyword)
>>>>          kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
>>>>          variadic = true;
>>>>          break;
>>>> +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
>>>> +      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
>>>> +      binary = true;
>>>> +      break;
>>>> +    case RID_REF_CONVERTS_FROM_TEMPORARY:
>>>> +      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
>>>> +      binary = true;
>>>> +      break;
>>>>        default:
>>>>          gcc_unreachable ();
>>>>        }
>>>> @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
>>>>      if (TYPE_REF_P (type))
>>>>        {
>>>> -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
>>>> +      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type,
>>>> expr))
>>>>        {
>>>>          auto_diagnostic_group d;
>>>>          if (warning_at (loc, OPT_Wrange_loop_construct,
>>>> @@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
>>>>      tree rtype = cp_build_reference_type (type, /*rval*/false);
>>>>      /* If we could initialize the reference directly, it wouldn't
>>>> involve any
>>>>         copies.  */
>>>> -  if (!ref_conv_binds_directly_p (rtype, expr))
>>>> +  if (ref_conv_binds_to_temporary_p (rtype, expr))
>>>>        return;
>>>
>>> I think this case wants the old handling of invalid conversions you
>>> mentioned in your intro; we don't want to suggest changing to a
>>> reference if that's ill-formed.
>>>
>>> In passing we might change the comment to "If we can initialize a
>>> reference directly, suggest that to avoid the copy." and move it above
>>> the rtype declaration.
>>
>> Hmm, and I suspect we get false positives when expr is a prvalue, so
>> initializing a non-reference variable of the same cv-unqualified type
>> involves no extra copy?
> 
> I couldn't provoke a false positive here.  Note that ref_conv_binds_to_temporary_p
> always gets a reference so copy elision isn't applicable here.

Ah, I see: if expr is a prvalue, that counts as binding a reference to a 
temporary, so we (properly) don't think the reference is an improvement.

The function name reference_from_temporary seems to me to suggest a tree 
value rather than bool, maybe rename to ref_xes_from_temporary?

OK either way.

Jason


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-15  3:48       ` Jason Merrill
@ 2022-07-15 15:32         ` Marek Polacek
  2022-07-15 19:59           ` [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Ville Voutilainen
  0 siblings, 1 reply; 15+ messages in thread
From: Marek Polacek @ 2022-07-15 15:32 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches, libstdc++, Jonathan Wakely

On Thu, Jul 14, 2022 at 11:48:51PM -0400, Jason Merrill wrote:
> On 7/14/22 13:43, Marek Polacek wrote:
> > On Tue, Jul 12, 2022 at 04:15:00PM -0400, Jason Merrill wrote:
> > > On 7/12/22 16:10, Jason Merrill wrote:
> > > > On 7/8/22 13:41, Marek Polacek wrote:
> > > > > This patch implements C++23 P2255R2, which adds two new type traits to
> > > > > detect reference binding to a temporary.  They can be used to detect code
> > > > > like
> > > > > 
> > > > >     std::tuple<const std::string&> t("meow");
> > > > > 
> > > > > which is incorrect because it always creates a dangling reference,
> > > > > because
> > > > > the std::string temporary is created inside the selected constructor of
> > > > > std::tuple, and not outside it.
> > > > > 
> > > > > There are two new compiler builtins,
> > > > > __reference_constructs_from_temporary
> > > > > and __reference_converts_from_temporary.  The former is used to simulate
> > > > > direct- and the latter copy-initialization context.  But I had a
> > > > > hard time
> > > > > finding a test where there's actually a difference.  Under DR 2267, both
> > > > > of these are invalid:
> > > > > 
> > > > >     struct A { } a;
> > > > >     struct B { explicit B(const A&); };
> > > > >     const B &b1{a};
> > > > >     const B &b2(a);
> > > > > 
> > > > > so I had to peruse [over.match.ref], and eventually realized that the
> > > > > difference can be seen here:
> > > > > 
> > > > >     struct G {
> > > > >       operator int(); // #1
> > > > >       explicit operator int&&(); // #2
> > > > >     };
> > > > > 
> > > > > int&& r1(G{}); // use #2 (no temporary)
> > > > > int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)
> > > > > 
> > > > > The implementation itself was rather straightforward because we already
> > > > > have conv_binds_ref_to_prvalue.  The main function here is
> > > > > reference_from_temporary.  The renaming to ref_conv_binds_to_temporary_p
> > > > > is because previously the function didn't distinguish between an invalid
> > > > > conversion and one that binds to a prvalue.
> > > > > 
> > > > > The patch also adds the relevant class and variable templates to
> > > > > <type_traits>.
> > > > > 
> > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > > > 
> > > > >      PR c++/104477
> > > > > 
> > > > > gcc/c-family/ChangeLog:
> > > > > 
> > > > >      * c-common.cc (c_common_reswords): Add
> > > > >      __reference_constructs_from_temporary and
> > > > >      __reference_converts_from_temporary.
> > > > >      * c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > >      RID_REF_CONVERTS_FROM_TEMPORARY.
> > > > > 
> > > > > gcc/cp/ChangeLog:
> > > > > 
> > > > >      * call.cc (ref_conv_binds_directly_p): Rename to ...
> > > > >      (ref_conv_binds_to_temporary_p): ... this.  Add a new bool
> > > > >      parameter.  Return true only if the conversion is valid and
> > > > >      conv_binds_ref_to_prvalue returns true.
> > > > >      * constraint.cc (diagnose_trait_expr): Handle
> > > > >      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > > > >      * cp-tree.h (enum cp_trait_kind): Add
> > > > > CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
> > > > >      and CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > > > >      (ref_conv_binds_directly_p): Rename to ...
> > > > >      (ref_conv_binds_to_temporary_p): ... this.
> > > > >      (reference_from_temporary): Declare.
> > > > >      * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
> > > > >      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > > > >      * method.cc (reference_from_temporary): New.
> > > > >      * parser.cc (cp_parser_primary_expression): Handle
> > > > >      RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > RID_REF_CONVERTS_FROM_TEMPORARY.
> > > > >      (cp_parser_trait_expr): Likewise.
> > > > >      (warn_for_range_copy): Adjust to call ref_conv_binds_to_temporary_p.
> > > > >      * semantics.cc (trait_expr_value): Handle
> > > > >      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > > > >      (finish_trait_expr): Likewise.
> > > > > 
> > > > > libstdc++-v3/ChangeLog:
> > > > > 
> > > > >      * include/std/type_traits (reference_constructs_from_temporary,
> > > > >      reference_converts_from_temporary): New class templates.
> > > > >      (reference_constructs_from_temporary_v,
> > > > >      reference_converts_from_temporary_v): New variable templates.
> > > > >      (__cpp_lib_reference_from_temporary): Define for C++23.
> > > > >      * include/std/version (__cpp_lib_reference_from_temporary):
> > > > > Define for
> > > > >      C++23.
> > > > >      * testsuite/20_util/variable_templates_for_traits.cc: Test
> > > > >      reference_constructs_from_temporary_v and
> > > > >      reference_converts_from_temporary_v.
> > > > >      * testsuite/20_util/reference_from_temporary/value.cc: New test.
> > > > >      * testsuite/20_util/reference_from_temporary/value2.cc: New test.
> > > > >      * testsuite/20_util/reference_from_temporary/version.cc: New test.
> > > > > 
> > > > > gcc/testsuite/ChangeLog:
> > > > > 
> > > > >      * g++.dg/ext/reference_constructs_from_temporary1.C: New test.
> > > > >      * g++.dg/ext/reference_converts_from_temporary1.C: New test.
> > > > > ---
> > > > >    gcc/c-family/c-common.cc                      |   4 +
> > > > >    gcc/c-family/c-common.h                       |   2 +
> > > > >    gcc/cp/call.cc                                |  14 +-
> > > > >    gcc/cp/constraint.cc                          |   8 +
> > > > >    gcc/cp/cp-tree.h                              |   7 +-
> > > > >    gcc/cp/cxx-pretty-print.cc                    |   6 +
> > > > >    gcc/cp/method.cc                              |  28 +++
> > > > >    gcc/cp/parser.cc                              |  14 +-
> > > > >    gcc/cp/semantics.cc                           |   8 +
> > > > >    .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
> > > > >    .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
> > > > >    libstdc++-v3/include/std/type_traits          |  39 ++++
> > > > >    libstdc++-v3/include/std/version              |   5 +-
> > > > >    .../20_util/reference_from_temporary/value.cc | 110 +++++++++
> > > > >    .../reference_from_temporary/value2.cc        |  28 +++
> > > > >    .../reference_from_temporary/version.cc       |  27 +++
> > > > >    .../20_util/variable_templates_for_traits.cc  |  14 ++
> > > > >    17 files changed, 730 insertions(+), 12 deletions(-)
> > > > >    create mode 100644
> > > > > gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> > > > >    create mode 100644
> > > > > gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> > > > >    create mode 100644
> > > > > libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> > > > >    create mode 100644
> > > > > libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> > > > >    create mode 100644
> > > > > libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> > > > > 
> > > > > diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> > > > > index 1b8e73f7bc5..655c3aefee6 100644
> > > > > --- a/gcc/c-family/c-common.cc
> > > > > +++ b/gcc/c-family/c-common.cc
> > > > > @@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
> > > > >      { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
> > > > >      { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
> > > > >      { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE,
> > > > > D_CXXONLY },
> > > > > +  { "__reference_constructs_from_temporary",
> > > > > RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > > > +                    D_CXXONLY },
> > > > > +  { "__reference_converts_from_temporary",
> > > > > RID_REF_CONVERTS_FROM_TEMPORARY,
> > > > > +                    D_CXXONLY },
> > > > >      /* C++ transactional memory.  */
> > > > >      { "synchronized",    RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
> > > > > diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> > > > > index c0900848965..f9064393b4e 100644
> > > > > --- a/gcc/c-family/c-common.h
> > > > > +++ b/gcc/c-family/c-common.h
> > > > > @@ -184,6 +184,8 @@ enum rid
> > > > >      RID_IS_UNION,                RID_UNDERLYING_TYPE,
> > > > >      RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
> > > > >      RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
> > > > > +  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > > > +  RID_REF_CONVERTS_FROM_TEMPORARY,
> > > > >      /* C++11 */
> > > > >      RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR,
> > > > > RID_STATIC_ASSERT,
> > > > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> > > > > index fc98552fda2..1ba209f61f1 100644
> > > > > --- a/gcc/cp/call.cc
> > > > > +++ b/gcc/cp/call.cc
> > > > > @@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
> > > > >      return conv_is_prvalue (next_conversion (c));
> > > > >    }
> > > > > -/* True iff converting EXPR to a reference type TYPE does not involve
> > > > > -   creating a temporary.  */
> > > > > +/* True iff converting EXPR to a reference type TYPE binds the
> > > > > reference to
> > > > > +   a temporary.  DIRECT_INIT_P says whether the conversion should
> > > > > be done
> > > > > +   in direct- or copy-initialization context.  */
> > > > >    bool
> > > > > -ref_conv_binds_directly_p (tree type, tree expr)
> > > > > +ref_conv_binds_to_temporary_p (tree type, tree expr,
> > > > > +                   bool direct_init_p /*= false*/)
> > > > >    {
> > > > >      gcc_assert (TYPE_REF_P (type));
> > > > >      /* Get the high-water mark for the CONVERSION_OBSTACK.  */
> > > > >      void *p = conversion_obstack_alloc (0);
> > > > > +  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
> > > > >      conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
> > > > > -                      /*c_cast_p=*/false,
> > > > > -                      LOOKUP_IMPLICIT, tf_none);
> > > > > -  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
> > > > > +                      /*c_cast_p=*/false, flags, tf_none);
> > > > > +  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue (conv);
> > > > >      /* Free all the conversions we allocated.  */
> > > > >      obstack_free (&conversion_obstack, p);
> > > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > > index 591155cee22..648cc9d176d 100644
> > > > > --- a/gcc/cp/constraint.cc
> > > > > +++ b/gcc/cp/constraint.cc
> > > > > @@ -3687,6 +3687,14 @@ diagnose_trait_expr (tree expr, tree args)
> > > > >        case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
> > > > >          inform (loc, "  %qT does not have unique object
> > > > > representations", t1);
> > > > >          break;
> > > > > +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > +      inform (loc, "  %qT is not a reference that binds to a temporary "
> > > > > +          "object of type %qT (direct-initialization)", t1, t2);
> > > > > +      break;
> > > > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > > > +      inform (loc, "  %qT is not a reference that binds to a temporary "
> > > > > +          "object of type %qT (copy-initialization)", t1, t2);
> > > > > +      break;
> > > > >        case CPTK_BASES:
> > > > >        case CPTK_DIRECT_BASES:
> > > > >        case CPTK_UNDERLYING_TYPE:
> > > > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > > > > index 2fde4f83b41..c3bed31e455 100644
> > > > > --- a/gcc/cp/cp-tree.h
> > > > > +++ b/gcc/cp/cp-tree.h
> > > > > @@ -1397,7 +1397,9 @@ enum cp_trait_kind
> > > > >      CPTK_IS_ASSIGNABLE,
> > > > >      CPTK_IS_CONSTRUCTIBLE,
> > > > >      CPTK_IS_NOTHROW_ASSIGNABLE,
> > > > > -  CPTK_IS_NOTHROW_CONSTRUCTIBLE
> > > > > +  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
> > > > > +  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > > > +  CPTK_REF_CONVERTS_FROM_TEMPORARY
> > > > >    };
> > > > >    /* The types that we are processing.  */
> > > > > @@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p
> > > > > (const_tree);
> > > > >    extern tree type_decays_to            (tree);
> > > > >    extern tree extract_call_expr            (tree);
> > > > >    extern tree build_trivial_dtor_call        (tree, bool = false);
> > > > > -extern bool ref_conv_binds_directly_p        (tree, tree);
> > > > > +extern bool ref_conv_binds_to_temporary_p    (tree, tree, bool = false);
> > > > >    extern tree build_user_type_conversion        (tree, tree, int,
> > > > >                             tsubst_flags_t);
> > > > >    extern tree build_new_function_call        (tree, vec<tree, va_gc> **,
> > > > > @@ -7105,6 +7107,7 @@ extern tree forward_parm            (tree);
> > > > >    extern bool is_trivially_xible            (enum tree_code, tree, tree);
> > > > >    extern bool is_nothrow_xible            (enum tree_code, tree, tree);
> > > > >    extern bool is_xible                (enum tree_code, tree, tree);
> > > > > +extern bool reference_from_temporary        (tree, tree, bool);
> > > > >    extern tree get_defaulted_eh_spec        (tree, tsubst_flags_t =
> > > > > tf_warning_or_error);
> > > > >    extern bool maybe_explain_implicit_delete    (tree);
> > > > >    extern void explain_implicit_non_constexpr    (tree);
> > > > > diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> > > > > index 7e4db2e413b..44590830a61 100644
> > > > > --- a/gcc/cp/cxx-pretty-print.cc
> > > > > +++ b/gcc/cp/cxx-pretty-print.cc
> > > > > @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer
> > > > > *pp, tree t)
> > > > >        case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
> > > > >          pp_cxx_ws_string (pp, "__is_nothrow_constructible");
> > > > >          break;
> > > > > +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > +      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
> > > > > +      break;
> > > > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > > > +      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
> > > > > +      break;
> > > > >        default:
> > > > >          gcc_unreachable ();
> > > > > diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> > > > > index 0dffd648b0b..dd9715b6725 100644
> > > > > --- a/gcc/cp/method.cc
> > > > > +++ b/gcc/cp/method.cc
> > > > > @@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to, tree from)
> > > > >      return !!expr;
> > > > >    }
> > > > > +/* Return true iff conjunction_v<is_reference<T>,
> > > > > is_constructible<T, U>> is
> > > > > +   true, and the initialization
> > > > > +     T t(VAL<U>); // DIRECT_INIT_P
> > > > > +   or
> > > > > +     T t = VAL<U>; // !DIRECT_INIT_P
> > > > > +   binds t to a temporary object whose lifetime is extended.
> > > > > +   VAL<T> is defined in [meta.unary.prop]:
> > > > > +   -- If T is a reference or function type, VAL<T> is an expression
> > > > > with the
> > > > > +   same type and value category as declval<T>().
> > > > > +   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
> > > > > +
> > > > > +bool
> > > > > +reference_from_temporary (tree to, tree from, bool direct_init_p)
> > > > > +{
> > > > > +  /* Check is_reference<T>.  */
> > > > > +  if (!TYPE_REF_P (to))
> > > > > +    return false;
> > > > > +  /* Check is_constructible<T, U>.
> > > > > +     ??? This check doesn't seem to be necessary; if T isn't
> > > > > constructible
> > > > > +     from U, we won't be able to create a conversion.  */
> > > > > +  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
> > > > > +    return false;
> > > > 
> > > > I agree with the comment, did you try leaving this out?  If it stays I'd
> > > > think it needs to consider direct_init_p.
> > > > 
> > > > > +  tree val = build_stub_object (from);
> > > > > +  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
> > > > > +    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) :
> > > > > rvalue (val);
> > > > > +  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
> > > > > +}
> > > > > +
> > > > >    /* Categorize various special_function_kinds.  */
> > > > >    #define SFK_CTOR_P(sfk) \
> > > > >      ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
> > > > > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> > > > > index bf9ea3685f8..edee94bda13 100644
> > > > > --- a/gcc/cp/parser.cc
> > > > > +++ b/gcc/cp/parser.cc
> > > > > @@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
> > > > >        case RID_IS_CONSTRUCTIBLE:
> > > > >        case RID_IS_NOTHROW_ASSIGNABLE:
> > > > >        case RID_IS_NOTHROW_CONSTRUCTIBLE:
> > > > > +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> > > > >          return cp_parser_trait_expr (parser, token->keyword);
> > > > >        // C++ concepts
> > > > > @@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser,
> > > > > enum rid keyword)
> > > > >          kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
> > > > >          variadic = true;
> > > > >          break;
> > > > > +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > +      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
> > > > > +      binary = true;
> > > > > +      break;
> > > > > +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> > > > > +      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
> > > > > +      binary = true;
> > > > > +      break;
> > > > >        default:
> > > > >          gcc_unreachable ();
> > > > >        }
> > > > > @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
> > > > >      if (TYPE_REF_P (type))
> > > > >        {
> > > > > -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
> > > > > +      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type,
> > > > > expr))
> > > > >        {
> > > > >          auto_diagnostic_group d;
> > > > >          if (warning_at (loc, OPT_Wrange_loop_construct,
> > > > > @@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
> > > > >      tree rtype = cp_build_reference_type (type, /*rval*/false);
> > > > >      /* If we could initialize the reference directly, it wouldn't
> > > > > involve any
> > > > >         copies.  */
> > > > > -  if (!ref_conv_binds_directly_p (rtype, expr))
> > > > > +  if (ref_conv_binds_to_temporary_p (rtype, expr))
> > > > >        return;
> > > > 
> > > > I think this case wants the old handling of invalid conversions you
> > > > mentioned in your intro; we don't want to suggest changing to a
> > > > reference if that's ill-formed.
> > > > 
> > > > In passing we might change the comment to "If we can initialize a
> > > > reference directly, suggest that to avoid the copy." and move it above
> > > > the rtype declaration.
> > > 
> > > Hmm, and I suspect we get false positives when expr is a prvalue, so
> > > initializing a non-reference variable of the same cv-unqualified type
> > > involves no extra copy?
> > 
> > I couldn't provoke a false positive here.  Note that ref_conv_binds_to_temporary_p
> > always gets a reference so copy elision isn't applicable here.
> 
> Ah, I see: if expr is a prvalue, that counts as binding a reference to a
> temporary, so we (properly) don't think the reference is an improvement.
> 
> The function name reference_from_temporary seems to me to suggest a tree
> value rather than bool, maybe rename to ref_xes_from_temporary?

I agree that that's a better name, so I've changed it.  I would've preferred
ref_xes_from_temporary_p but is_xible et al don't have the _p.

I'm going to push this, thanks!

Tested x86_64-pc-linux-gnu, applying to trunk.

-- >8 --
This patch implements C++23 P2255R2, which adds two new type traits to
detect reference binding to a temporary.  They can be used to detect code
like

  std::tuple<const std::string&> t("meow");

which is incorrect because it always creates a dangling reference, because
the std::string temporary is created inside the selected constructor of
std::tuple, and not outside it.

There are two new compiler builtins, __reference_constructs_from_temporary
and __reference_converts_from_temporary.  The former is used to simulate
direct- and the latter copy-initialization context.  But I had a hard time
finding a test where there's actually a difference.  Under DR 2267, both
of these are invalid:

  struct A { } a;
  struct B { explicit B(const A&); };
  const B &b1{a};
  const B &b2(a);

so I had to peruse [over.match.ref], and eventually realized that the
difference can be seen here:

  struct G {
    operator int(); // #1
    explicit operator int&&(); // #2
  };

int&& r1(G{}); // use #2 (no temporary)
int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)

The implementation itself was rather straightforward because we already
have the conv_binds_ref_to_prvalue function.  The main function here is
ref_xes_from_temporary.
I've changed the return type of ref_conv_binds_directly to tristate, because
previously the function didn't distinguish between an invalid conversion and
one that binds to a prvalue.  Since it no longer returns a bool, I removed
the _p suffix.

The patch also adds the relevant class and variable templates to <type_traits>.

	PR c++/104477

gcc/c-family/ChangeLog:

	* c-common.cc (c_common_reswords): Add
	__reference_constructs_from_temporary and
	__reference_converts_from_temporary.
	* c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
	RID_REF_CONVERTS_FROM_TEMPORARY.

gcc/cp/ChangeLog:

	* call.cc (ref_conv_binds_directly_p): Rename to ...
	(ref_conv_binds_directly): ... this.  Add a new bool parameter.  Change
	the return type to tristate.
	* constraint.cc (diagnose_trait_expr): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	* cp-tree.h: Include "tristate.h".
	(enum cp_trait_kind): Add CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
	and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	(ref_conv_binds_directly_p): Rename to ...
	(ref_conv_binds_directly): ... this.
	(ref_xes_from_temporary): Declare.
	* cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	* method.cc (ref_xes_from_temporary): New.
	* parser.cc (cp_parser_primary_expression): Handle
	RID_REF_CONSTRUCTS_FROM_TEMPORARY and RID_REF_CONVERTS_FROM_TEMPORARY.
	(cp_parser_trait_expr): Likewise.
	(warn_for_range_copy): Adjust to call ref_conv_binds_directly.
	* semantics.cc (trait_expr_value): Handle
	CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and CPTK_REF_CONVERTS_FROM_TEMPORARY.
	(finish_trait_expr): Likewise.

libstdc++-v3/ChangeLog:

	* include/std/type_traits (reference_constructs_from_temporary,
	reference_converts_from_temporary): New class templates.
	(reference_constructs_from_temporary_v,
	reference_converts_from_temporary_v): New variable templates.
	(__cpp_lib_reference_from_temporary): Define for C++23.
	* include/std/version (__cpp_lib_reference_from_temporary): Define for
	C++23.
	* testsuite/20_util/variable_templates_for_traits.cc: Test
	reference_constructs_from_temporary_v and
	reference_converts_from_temporary_v.
	* testsuite/20_util/reference_from_temporary/value.cc: New test.
	* testsuite/20_util/reference_from_temporary/value2.cc: New test.
	* testsuite/20_util/reference_from_temporary/version.cc: New test.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/reference_constructs_from_temporary1.C: New test.
	* g++.dg/ext/reference_converts_from_temporary1.C: New test.
---
 gcc/c-family/c-common.cc                      |   4 +
 gcc/c-family/c-common.h                       |   2 +
 gcc/cp/call.cc                                |  20 +-
 gcc/cp/constraint.cc                          |   8 +
 gcc/cp/cp-tree.h                              |   8 +-
 gcc/cp/cxx-pretty-print.cc                    |   6 +
 gcc/cp/method.cc                              |  25 ++
 gcc/cp/parser.cc                              |  36 +--
 gcc/cp/semantics.cc                           |   8 +
 .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
 .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
 libstdc++-v3/include/std/type_traits          |  39 ++++
 libstdc++-v3/include/std/version              |   5 +-
 .../20_util/reference_from_temporary/value.cc | 110 +++++++++
 .../reference_from_temporary/value2.cc        |  28 +++
 .../reference_from_temporary/version.cc       |  27 +++
 .../20_util/variable_templates_for_traits.cc  |  14 ++
 17 files changed, 744 insertions(+), 24 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 1b8e73f7bc5..655c3aefee6 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
   { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
   { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
   { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY },
+  { "__reference_constructs_from_temporary", RID_REF_CONSTRUCTS_FROM_TEMPORARY,
+					D_CXXONLY },
+  { "__reference_converts_from_temporary", RID_REF_CONVERTS_FROM_TEMPORARY,
+					D_CXXONLY },
 
   /* C++ transactional memory.  */
   { "synchronized",	RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c0900848965..f9064393b4e 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -184,6 +184,8 @@ enum rid
   RID_IS_UNION,                RID_UNDERLYING_TYPE,
   RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
   RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
+  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
+  RID_REF_CONVERTS_FROM_TEMPORARY,
 
   /* C++11 */
   RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index fc98552fda2..191c68cdcfd 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9109,21 +9109,27 @@ conv_binds_ref_to_prvalue (conversion *c)
   return conv_is_prvalue (next_conversion (c));
 }
 
-/* True iff converting EXPR to a reference type TYPE does not involve
-   creating a temporary.  */
+/* Return tristate::TS_TRUE if converting EXPR to a reference type TYPE does
+   not involve creating a temporary.  Return tristate::TS_FALSE if converting
+   EXPR to a reference type TYPE binds the reference to a temporary.  If the
+   conversion is invalid or bad, return tristate::TS_UNKNOWN.  DIRECT_INIT_P
+   says whether the conversion should be done in direct- or copy-initialization
+   context.  */
 
-bool
-ref_conv_binds_directly_p (tree type, tree expr)
+tristate
+ref_conv_binds_directly (tree type, tree expr, bool direct_init_p /*= false*/)
 {
   gcc_assert (TYPE_REF_P (type));
 
   /* Get the high-water mark for the CONVERSION_OBSTACK.  */
   void *p = conversion_obstack_alloc (0);
 
+  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
   conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
-					  /*c_cast_p=*/false,
-					  LOOKUP_IMPLICIT, tf_none);
-  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
+					  /*c_cast_p=*/false, flags, tf_none);
+  tristate ret (tristate::TS_UNKNOWN);
+  if (conv && !conv->bad_p)
+    ret = tristate (!conv_binds_ref_to_prvalue (conv));
 
   /* Free all the conversions we allocated.  */
   obstack_free (&conversion_obstack, p);
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index f2137eb7aa1..568318f0ba1 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3697,6 +3697,14 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
       inform (loc, "  %qT does not have unique object representations", t1);
       break;
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      inform (loc, "  %qT is not a reference that binds to a temporary "
+	      "object of type %qT (direct-initialization)", t1, t2);
+      break;
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      inform (loc, "  %qT is not a reference that binds to a temporary "
+	      "object of type %qT (copy-initialization)", t1, t2);
+      break;
     case CPTK_BASES:
     case CPTK_DIRECT_BASES:
     case CPTK_UNDERLYING_TYPE:
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index bec98aa2ac3..cf51c39dc90 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tm.h"
 #include "hard-reg-set.h"
 #include "function.h"
+#include "tristate.h"
 
 /* In order for the format checking to accept the C++ front end
    diagnostic framework extensions, you must include this file before
@@ -1397,7 +1398,9 @@ enum cp_trait_kind
   CPTK_IS_ASSIGNABLE,
   CPTK_IS_CONSTRUCTIBLE,
   CPTK_IS_NOTHROW_ASSIGNABLE,
-  CPTK_IS_NOTHROW_CONSTRUCTIBLE
+  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
+  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
+  CPTK_REF_CONVERTS_FROM_TEMPORARY
 };
 
 /* The types that we are processing.  */
@@ -6520,7 +6523,7 @@ extern bool sufficient_parms_p			(const_tree);
 extern tree type_decays_to			(tree);
 extern tree extract_call_expr			(tree);
 extern tree build_trivial_dtor_call		(tree, bool = false);
-extern bool ref_conv_binds_directly_p		(tree, tree);
+extern tristate ref_conv_binds_directly		(tree, tree, bool = false);
 extern tree build_user_type_conversion		(tree, tree, int,
 						 tsubst_flags_t);
 extern tree build_new_function_call		(tree, vec<tree, va_gc> **,
@@ -7105,6 +7108,7 @@ extern tree forward_parm			(tree);
 extern bool is_trivially_xible			(enum tree_code, tree, tree);
 extern bool is_nothrow_xible			(enum tree_code, tree, tree);
 extern bool is_xible				(enum tree_code, tree, tree);
+extern bool ref_xes_from_temporary		(tree, tree, bool);
 extern tree get_defaulted_eh_spec		(tree, tsubst_flags_t = tf_warning_or_error);
 extern bool maybe_explain_implicit_delete	(tree);
 extern void explain_implicit_non_constexpr	(tree);
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 7e4db2e413b..44590830a61 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t)
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
       pp_cxx_ws_string (pp, "__is_nothrow_constructible");
       break;
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
+      break;
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
+      break;
 
     default:
       gcc_unreachable ();
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 0dffd648b0b..f2050f6e970 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -2211,6 +2211,31 @@ is_xible (enum tree_code code, tree to, tree from)
   return !!expr;
 }
 
+/* Return true iff conjunction_v<is_reference<T>, is_constructible<T, U>> is
+   true, and the initialization
+     T t(VAL<U>); // DIRECT_INIT_P
+   or
+     T t = VAL<U>; // !DIRECT_INIT_P
+   binds t to a temporary object whose lifetime is extended.
+   VAL<T> is defined in [meta.unary.prop]:
+   -- If T is a reference or function type, VAL<T> is an expression with the
+   same type and value category as declval<T>().
+   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
+
+bool
+ref_xes_from_temporary (tree to, tree from, bool direct_init_p)
+{
+  /* Check is_reference<T>.  */
+  if (!TYPE_REF_P (to))
+    return false;
+  /* We don't check is_constructible<T, U>: if T isn't constructible
+     from U, we won't be able to create a conversion.  */
+  tree val = build_stub_object (from);
+  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
+    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) : rvalue (val);
+  return ref_conv_binds_directly (to, val, direct_init_p).is_false ();
+}
+
 /* Categorize various special_function_kinds.  */
 #define SFK_CTOR_P(sfk) \
   ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index bf9ea3685f8..4f67441eeb1 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
 	case RID_IS_CONSTRUCTIBLE:
 	case RID_IS_NOTHROW_ASSIGNABLE:
 	case RID_IS_NOTHROW_CONSTRUCTIBLE:
+	case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
+	case RID_REF_CONVERTS_FROM_TEMPORARY:
 	  return cp_parser_trait_expr (parser, token->keyword);
 
 	// C++ concepts
@@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
       kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
       variadic = true;
       break;
+    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
+      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
+      binary = true;
+      break;
+    case RID_REF_CONVERTS_FROM_TEMPORARY:
+      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
+      binary = true;
+      break;
     default:
       gcc_unreachable ();
     }
@@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
 
   if (TYPE_REF_P (type))
     {
-      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
+      if (glvalue_p (expr) && ref_conv_binds_directly (type, expr).is_false ())
 	{
 	  auto_diagnostic_group d;
 	  if (warning_at (loc, OPT_Wrange_loop_construct,
@@ -13839,20 +13849,20 @@ warn_for_range_copy (tree decl, tree expr)
 	  && trivially_copyable_p (type)))
     return;
 
+  /* If we can initialize a reference directly, suggest that to avoid the
+     copy.  */
   tree rtype = cp_build_reference_type (type, /*rval*/false);
-  /* If we could initialize the reference directly, it wouldn't involve any
-     copies.  */
-  if (!ref_conv_binds_directly_p (rtype, expr))
-    return;
-
-  auto_diagnostic_group d;
-  if (warning_at (loc, OPT_Wrange_loop_construct,
-		  "loop variable %qD creates a copy from type %qT",
-		  decl, type))
+  if (ref_conv_binds_directly (rtype, expr).is_true ())
     {
-      gcc_rich_location richloc (loc);
-      richloc.add_fixit_insert_before ("&");
-      inform (&richloc, "use reference type to prevent copying");
+      auto_diagnostic_group d;
+      if (warning_at (loc, OPT_Wrange_loop_construct,
+		      "loop variable %qD creates a copy from type %qT",
+		      decl, type))
+	{
+	  gcc_rich_location richloc (loc);
+	  richloc.add_fixit_insert_before ("&");
+	  inform (&richloc, "use reference type to prevent copying");
+	}
     }
 }
 
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 2344b5eea00..96037c21b85 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -12007,6 +12007,12 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
       return is_nothrow_xible (INIT_EXPR, type1, type2);
 
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+      return ref_xes_from_temporary (type1, type2, /*direct_init=*/true);
+
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
+      return ref_xes_from_temporary (type1, type2, /*direct_init=*/false);
+
     default:
       gcc_unreachable ();
       return false;
@@ -12088,6 +12094,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
     case CPTK_IS_NOTHROW_ASSIGNABLE:
     case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
       if (!check_trait_type (type1)
 	  || !check_trait_type (type2))
 	return error_mark_node;
diff --git a/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
new file mode 100644
index 00000000000..76de905a35d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
@@ -0,0 +1,214 @@
+// P2255R2
+// PR c++/104477
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+struct A { A(); };
+struct B { operator A(); };
+struct C { explicit C(); };
+struct D { explicit operator A(); };
+struct E { explicit operator A&(); };
+struct F { explicit operator A&&(); };
+// Could use a class template with explicit(bool), but then this would need
+// C++20.
+struct G {
+  operator int();
+  explicit operator int&&();
+};
+struct G2 {
+  explicit operator int();
+  operator int&&();
+};
+struct H {
+  operator int();
+  explicit operator int&();
+};
+struct H2 {
+  explicit operator int();
+  operator int&();
+};
+
+struct Base { };
+struct Der : Base { };
+
+template<typename T, typename RT>
+struct morph {
+  mutable T val{};
+  operator RT() const { return static_cast<RT>(val); }
+};
+
+template<typename T> using id = T;
+
+// Built-in types.
+SA(!__reference_constructs_from_temporary(int, int));
+SA(!__reference_constructs_from_temporary(int&, void));
+SA(!__reference_constructs_from_temporary(int&, const void));
+SA(!__reference_constructs_from_temporary(int&, volatile void));
+SA(!__reference_constructs_from_temporary(int&, const volatile void));
+SA(!__reference_constructs_from_temporary(void, void));
+SA(!__reference_constructs_from_temporary(void, int));
+SA(!__reference_constructs_from_temporary(int&, int));
+SA(!__reference_constructs_from_temporary(int&, int&));
+SA(!__reference_constructs_from_temporary(int&, int&&));
+SA(!__reference_constructs_from_temporary(int&, long));
+// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
+SA(!__reference_constructs_from_temporary(int&, long&));
+SA(!__reference_constructs_from_temporary(int&, long&&));
+SA( __reference_constructs_from_temporary(const int&, int));
+SA(!__reference_constructs_from_temporary(const int&, int&));
+SA(!__reference_constructs_from_temporary(const int&, const int&));
+SA(!__reference_constructs_from_temporary(const int&, int&&));
+SA( __reference_constructs_from_temporary(const int&, long));
+SA( __reference_constructs_from_temporary(const int&, long&));
+SA( __reference_constructs_from_temporary(const int&, long&&));
+SA( __reference_constructs_from_temporary(int&&, int));
+SA(!__reference_constructs_from_temporary(int&&, int&));
+SA(!__reference_constructs_from_temporary(int&&, int&&));
+SA( __reference_constructs_from_temporary(int&&, long));
+SA( __reference_constructs_from_temporary(int&&, long&));
+SA( __reference_constructs_from_temporary(int&&, long&&));
+SA(!__reference_constructs_from_temporary(unsigned int&, double));
+SA(!__reference_constructs_from_temporary(volatile int&, int));
+SA(!__reference_constructs_from_temporary(const volatile int&, int));
+SA(!__reference_constructs_from_temporary(volatile int&, int&));
+SA(!__reference_constructs_from_temporary(const volatile int&, int&));
+SA(!__reference_constructs_from_temporary(volatile int&, int&&));
+SA(!__reference_constructs_from_temporary(const volatile int&, int&&));
+
+// Classes.
+SA(!__reference_constructs_from_temporary(A, A));
+// A& r(A{}); doesn't construct.
+SA(!__reference_constructs_from_temporary(A&, A));
+SA(!__reference_constructs_from_temporary(A&, A&));
+SA(!__reference_constructs_from_temporary(A&, A&&));
+// Here we get const struct A & r = (const struct A &) &D.2414;
+SA( __reference_constructs_from_temporary(const A&, A));
+SA(!__reference_constructs_from_temporary(const A&, A&));
+SA(!__reference_constructs_from_temporary(const A&, const A&));
+SA(!__reference_constructs_from_temporary(const A&, A&&));
+// Here we get struct A & r = (struct A &) &D.2439;
+SA( __reference_constructs_from_temporary(A&&, A));
+SA(!__reference_constructs_from_temporary(A&&, A&));
+SA(!__reference_constructs_from_temporary(A&&, const A&));
+SA(!__reference_constructs_from_temporary(A&&, A&&));
+
+SA(!__reference_constructs_from_temporary(A, B));
+SA(!__reference_constructs_from_temporary(A&, B));
+SA(!__reference_constructs_from_temporary(A&, B&));
+SA(!__reference_constructs_from_temporary(A&, const B&));
+SA(!__reference_constructs_from_temporary(A&, B&&));
+SA( __reference_constructs_from_temporary(const A&, B));
+SA( __reference_constructs_from_temporary(const A&, B&));
+// Doesn't construct, so it's false.
+SA(!__reference_constructs_from_temporary(const A&, const B&));
+SA( __reference_constructs_from_temporary(const A&, B&&));
+SA( __reference_constructs_from_temporary(A&&, B));
+SA( __reference_constructs_from_temporary(A&&, B&));
+SA(!__reference_constructs_from_temporary(A&&, const B&));
+SA( __reference_constructs_from_temporary(A&&, B&&));
+
+SA(!__reference_constructs_from_temporary(const A&, C));
+SA(!__reference_constructs_from_temporary(const A&, C&));
+
+// Doesn't construct, so it's false.
+SA(!__reference_constructs_from_temporary(int&, morph<int, int>));
+// Here we get
+//   const int & r2 = D.2580 = morph<int, int>::operator int
+//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
+SA( __reference_constructs_from_temporary(const int&, morph<int, int>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, int&>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, const int&>));
+SA(!__reference_constructs_from_temporary(int&, morph<int, int&&>));
+SA( __reference_constructs_from_temporary(const int&, morph<long, long&>));
+
+// These are like const C& c(cref); so the explicit ctor C isn't a problem
+// even in copy-init context.  const C& r = {}; would be a different story.
+SA(!__reference_constructs_from_temporary(C, C));
+SA(!__reference_constructs_from_temporary(C&, C));
+SA(!__reference_constructs_from_temporary(C&, C&));
+SA(!__reference_constructs_from_temporary(C&, C&&));
+SA( __reference_constructs_from_temporary(const C&, C));
+SA(!__reference_constructs_from_temporary(const C&, C&));
+SA(!__reference_constructs_from_temporary(const C&, const C&));
+SA(!__reference_constructs_from_temporary(const C&, C&&));
+SA( __reference_constructs_from_temporary(C&&, C));
+SA(!__reference_constructs_from_temporary(C&&, C&));
+SA(!__reference_constructs_from_temporary(C&&, const C&));
+SA(!__reference_constructs_from_temporary(C&&, C&&));
+
+// These are all false ultimately because of CWG 2267, which we implement.
+SA(!__reference_constructs_from_temporary(A, D));
+SA(!__reference_constructs_from_temporary(A&, D));
+SA(!__reference_constructs_from_temporary(A&, D&));
+SA(!__reference_constructs_from_temporary(A&, const D&));
+SA(!__reference_constructs_from_temporary(A&, D&&));
+SA(!__reference_constructs_from_temporary(const A&, D));
+SA(!__reference_constructs_from_temporary(const A&, D&));
+SA(!__reference_constructs_from_temporary(const A&, const D&));
+SA(!__reference_constructs_from_temporary(const A&, D&&));
+SA(!__reference_constructs_from_temporary(A&&, D));
+SA(!__reference_constructs_from_temporary(A&&, D&));
+SA(!__reference_constructs_from_temporary(A&&, const D&));
+SA(!__reference_constructs_from_temporary(A&&, D&&));
+
+SA(!__reference_constructs_from_temporary(A, E));
+/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
+   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
+   so we're not binding the reference to a temporary, although there is
+   a temporary involved.  So the result is false in both copy- and direct-
+   init, albeit for different reasons!  */
+SA(!__reference_constructs_from_temporary(A&, E));
+// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
+SA(!__reference_constructs_from_temporary(A&, E&));
+SA(!__reference_constructs_from_temporary(A&, const E&));
+SA(!__reference_constructs_from_temporary(A&, E&&));
+// direct-init:
+// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
+SA(!__reference_constructs_from_temporary(const A&, E));
+SA(!__reference_constructs_from_temporary(const A&, E&));
+SA(!__reference_constructs_from_temporary(const A&, const E&));
+SA(!__reference_constructs_from_temporary(const A&, E&&));
+SA(!__reference_constructs_from_temporary(A&&, E));
+SA(!__reference_constructs_from_temporary(A&&, E&));
+SA(!__reference_constructs_from_temporary(A&&, const E&));
+SA(!__reference_constructs_from_temporary(A&&, E&&));
+
+SA(!__reference_constructs_from_temporary(A, F));
+// A& a1(F{}); and A& a2 = F{}; both invalid.
+SA(!__reference_constructs_from_temporary(A&, F));
+SA(!__reference_constructs_from_temporary(A&, F&));
+SA(!__reference_constructs_from_temporary(A&, const F&));
+SA(!__reference_constructs_from_temporary(A&, F&&));
+SA(!__reference_constructs_from_temporary(const A&, F));
+SA(!__reference_constructs_from_temporary(const A&, F&));
+SA(!__reference_constructs_from_temporary(const A&, const F&));
+SA(!__reference_constructs_from_temporary(const A&, F&&));
+SA(!__reference_constructs_from_temporary(A&&, F));
+SA(!__reference_constructs_from_temporary(A&&, F&));
+SA(!__reference_constructs_from_temporary(A&&, const F&));
+SA(!__reference_constructs_from_temporary(A&&, F&&));
+
+/* This is where _converts_ and _constructs_ will differ:
+   in direct-init we use G::operator int&& (no temporary),
+   but in copy-init we use G::operator int, where a temporary is created
+   to be bound to int&&.  */
+SA(!__reference_constructs_from_temporary(int&&, G));
+// Similar to the previous one.
+SA(!__reference_constructs_from_temporary(const int&, H));
+/* And here I've switched the explicit-ness.  In both copy- and direct-init
+   we call operator int&, so no temporary.  */
+SA(!__reference_constructs_from_temporary(int&&, G2));
+SA(!__reference_constructs_from_temporary(const int&, H2));
+
+SA(!__reference_constructs_from_temporary(const Base&, Der));
+
+// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
+SA(!__reference_constructs_from_temporary(int&&, id<int[3]>));
+
+// Arrays.
+SA(!__reference_constructs_from_temporary(int, int[]));
+SA(!__reference_constructs_from_temporary(int[], int[]));
+SA(!__reference_constructs_from_temporary(int&, int[]));
+SA(!__reference_constructs_from_temporary(int&&, int[]));
+SA(!__reference_constructs_from_temporary(const int&, int[]));
diff --git a/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
new file mode 100644
index 00000000000..90196c38742
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
@@ -0,0 +1,214 @@
+// P2255R2
+// PR c++/104477
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+struct A { A(); };
+struct B { operator A(); };
+struct C { explicit C(); };
+struct D { explicit operator A(); };
+struct E { explicit operator A&(); };
+struct F { explicit operator A&&(); };
+// Could use a class template with explicit(bool), but then this would need
+// C++20.
+struct G {
+  operator int();
+  explicit operator int&&();
+};
+struct G2 {
+  explicit operator int();
+  operator int&&();
+};
+struct H {
+  operator int();
+  explicit operator int&();
+};
+struct H2 {
+  explicit operator int();
+  operator int&();
+};
+
+struct Base { };
+struct Der : Base { };
+
+template<typename T, typename RT>
+struct morph {
+  mutable T val{};
+  operator RT() const { return static_cast<RT>(val); }
+};
+
+template<typename T> using id = T;
+
+// Built-in types.
+SA(!__reference_converts_from_temporary(int, int));
+SA(!__reference_converts_from_temporary(int&, void));
+SA(!__reference_converts_from_temporary(int&, const void));
+SA(!__reference_converts_from_temporary(int&, volatile void));
+SA(!__reference_converts_from_temporary(int&, const volatile void));
+SA(!__reference_converts_from_temporary(void, void));
+SA(!__reference_converts_from_temporary(void, int));
+SA(!__reference_converts_from_temporary(int&, int));
+SA(!__reference_converts_from_temporary(int&, int&));
+SA(!__reference_converts_from_temporary(int&, int&&));
+SA(!__reference_converts_from_temporary(int&, long));
+// non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'long'
+SA(!__reference_converts_from_temporary(int&, long&));
+SA(!__reference_converts_from_temporary(int&, long&&));
+SA( __reference_converts_from_temporary(const int&, int));
+SA(!__reference_converts_from_temporary(const int&, int&));
+SA(!__reference_converts_from_temporary(const int&, const int&));
+SA(!__reference_converts_from_temporary(const int&, int&&));
+SA( __reference_converts_from_temporary(const int&, long));
+SA( __reference_converts_from_temporary(const int&, long&));
+SA( __reference_converts_from_temporary(const int&, long&&));
+SA( __reference_converts_from_temporary(int&&, int));
+SA(!__reference_converts_from_temporary(int&&, int&));
+SA(!__reference_converts_from_temporary(int&&, int&&));
+SA( __reference_converts_from_temporary(int&&, long));
+SA( __reference_converts_from_temporary(int&&, long&));
+SA( __reference_converts_from_temporary(int&&, long&&));
+SA(!__reference_converts_from_temporary(unsigned int&, double));
+SA(!__reference_converts_from_temporary(volatile int&, int));
+SA(!__reference_converts_from_temporary(const volatile int&, int));
+SA(!__reference_converts_from_temporary(volatile int&, int&));
+SA(!__reference_converts_from_temporary(const volatile int&, int&));
+SA(!__reference_converts_from_temporary(volatile int&, int&&));
+SA(!__reference_converts_from_temporary(const volatile int&, int&&));
+
+// Classes.
+SA(!__reference_converts_from_temporary(A, A));
+// A& r(A{}); doesn't construct.
+SA(!__reference_converts_from_temporary(A&, A));
+SA(!__reference_converts_from_temporary(A&, A&));
+SA(!__reference_converts_from_temporary(A&, A&&));
+// Here we get const struct A & r = (const struct A &) &D.2414;
+SA( __reference_converts_from_temporary(const A&, A));
+SA(!__reference_converts_from_temporary(const A&, A&));
+SA(!__reference_converts_from_temporary(const A&, const A&));
+SA(!__reference_converts_from_temporary(const A&, A&&));
+// Here we get struct A & r = (struct A &) &D.2439;
+SA( __reference_converts_from_temporary(A&&, A));
+SA(!__reference_converts_from_temporary(A&&, A&));
+SA(!__reference_converts_from_temporary(A&&, const A&));
+SA(!__reference_converts_from_temporary(A&&, A&&));
+
+SA(!__reference_converts_from_temporary(A, B));
+SA(!__reference_converts_from_temporary(A&, B));
+SA(!__reference_converts_from_temporary(A&, B&));
+SA(!__reference_converts_from_temporary(A&, const B&));
+SA(!__reference_converts_from_temporary(A&, B&&));
+SA( __reference_converts_from_temporary(const A&, B));
+SA( __reference_converts_from_temporary(const A&, B&));
+// Doesn't construct, so it's false.
+SA(!__reference_converts_from_temporary(const A&, const B&));
+SA( __reference_converts_from_temporary(const A&, B&&));
+SA( __reference_converts_from_temporary(A&&, B));
+SA( __reference_converts_from_temporary(A&&, B&));
+SA(!__reference_converts_from_temporary(A&&, const B&));
+SA( __reference_converts_from_temporary(A&&, B&&));
+
+SA(!__reference_converts_from_temporary(const A&, C));
+SA(!__reference_converts_from_temporary(const A&, C&));
+
+// Doesn't construct, so it's false.
+SA(!__reference_converts_from_temporary(int&, morph<int, int>));
+// Here we get
+//   const int & r2 = D.2580 = morph<int, int>::operator int
+//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
+SA( __reference_converts_from_temporary(const int&, morph<int, int>));
+SA(!__reference_converts_from_temporary(int&, morph<int, int&>));
+SA(!__reference_converts_from_temporary(int&, morph<int, const int&>));
+SA(!__reference_converts_from_temporary(int&, morph<int, int&&>));
+SA( __reference_converts_from_temporary(const int&, morph<long, long&>));
+
+// These are like const C& c(cref); so the explicit ctor C isn't a problem
+// even in copy-init context.  const C& r = {}; would be a different story.
+SA(!__reference_converts_from_temporary(C, C));
+SA(!__reference_converts_from_temporary(C&, C));
+SA(!__reference_converts_from_temporary(C&, C&));
+SA(!__reference_converts_from_temporary(C&, C&&));
+SA( __reference_converts_from_temporary(const C&, C));
+SA(!__reference_converts_from_temporary(const C&, C&));
+SA(!__reference_converts_from_temporary(const C&, const C&));
+SA(!__reference_converts_from_temporary(const C&, C&&));
+SA( __reference_converts_from_temporary(C&&, C));
+SA(!__reference_converts_from_temporary(C&&, C&));
+SA(!__reference_converts_from_temporary(C&&, const C&));
+SA(!__reference_converts_from_temporary(C&&, C&&));
+
+// These are all false ultimately because of CWG 2267, which we implement.
+SA(!__reference_converts_from_temporary(A, D));
+SA(!__reference_converts_from_temporary(A&, D));
+SA(!__reference_converts_from_temporary(A&, D&));
+SA(!__reference_converts_from_temporary(A&, const D&));
+SA(!__reference_converts_from_temporary(A&, D&&));
+SA(!__reference_converts_from_temporary(const A&, D));
+SA(!__reference_converts_from_temporary(const A&, D&));
+SA(!__reference_converts_from_temporary(const A&, const D&));
+SA(!__reference_converts_from_temporary(const A&, D&&));
+SA(!__reference_converts_from_temporary(A&&, D));
+SA(!__reference_converts_from_temporary(A&&, D&));
+SA(!__reference_converts_from_temporary(A&&, const D&));
+SA(!__reference_converts_from_temporary(A&&, D&&));
+
+SA(!__reference_converts_from_temporary(A, E));
+/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
+   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715, {}>)
+   so we're not binding the reference to a temporary, although there is
+   a temporary involved.  So the result is false in both copy- and direct-
+   init, albeit for different reasons!  */
+SA(!__reference_converts_from_temporary(A&, E));
+// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
+SA(!__reference_converts_from_temporary(A&, E&));
+SA(!__reference_converts_from_temporary(A&, const E&));
+SA(!__reference_converts_from_temporary(A&, E&&));
+// direct-init:
+// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720, {}>)
+SA(!__reference_converts_from_temporary(const A&, E));
+SA(!__reference_converts_from_temporary(const A&, E&));
+SA(!__reference_converts_from_temporary(const A&, const E&));
+SA(!__reference_converts_from_temporary(const A&, E&&));
+SA(!__reference_converts_from_temporary(A&&, E));
+SA(!__reference_converts_from_temporary(A&&, E&));
+SA(!__reference_converts_from_temporary(A&&, const E&));
+SA(!__reference_converts_from_temporary(A&&, E&&));
+
+SA(!__reference_converts_from_temporary(A, F));
+// A& a1(F{}); and A& a2 = F{}; both invalid.
+SA(!__reference_converts_from_temporary(A&, F));
+SA(!__reference_converts_from_temporary(A&, F&));
+SA(!__reference_converts_from_temporary(A&, const F&));
+SA(!__reference_converts_from_temporary(A&, F&&));
+SA(!__reference_converts_from_temporary(const A&, F));
+SA(!__reference_converts_from_temporary(const A&, F&));
+SA(!__reference_converts_from_temporary(const A&, const F&));
+SA(!__reference_converts_from_temporary(const A&, F&&));
+SA(!__reference_converts_from_temporary(A&&, F));
+SA(!__reference_converts_from_temporary(A&&, F&));
+SA(!__reference_converts_from_temporary(A&&, const F&));
+SA(!__reference_converts_from_temporary(A&&, F&&));
+
+/* This is where _converts_ and _constructs_ will differ:
+   in direct-init we use G::operator int&& (no temporary),
+   but in copy-init we use G::operator int, where a temporary is created
+   to be bound to int&&.  */
+SA( __reference_converts_from_temporary(int&&, G));
+// Similar to the previous one.
+SA( __reference_converts_from_temporary(const int&, H));
+/* And here I've switched the explicit-ness.  In both copy- and direct-init
+   we call operator int&, so no temporary.  */
+SA(!__reference_converts_from_temporary(int&&, G2));
+SA(!__reference_converts_from_temporary(const int&, H2));
+
+SA(!__reference_converts_from_temporary(const Base&, Der));
+
+// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
+SA(!__reference_converts_from_temporary(int&&, id<int[3]>));
+
+// Arrays.
+SA(!__reference_converts_from_temporary(int, int[]));
+SA(!__reference_converts_from_temporary(int[], int[]));
+SA(!__reference_converts_from_temporary(int&, int[]));
+SA(!__reference_converts_from_temporary(int&&, int[]));
+SA(!__reference_converts_from_temporary(const int&, int[]));
diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index e5f58bc2e3f..b1a1deecf66 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -3505,6 +3505,45 @@ template<typename _Ret, typename _Fn, typename... _Args>
   template<typename _Tp>
     inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
 
+#define __cpp_lib_reference_from_temporary 202202L
+
+  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
+  /// direct-initialization, and a temporary object would be bound to
+  /// the reference, false otherwise.
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    struct reference_constructs_from_temporary
+    : public bool_constant<__reference_constructs_from_temporary(_Tp, _Up)>
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
+		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
+	"template argument must be a complete class or an unbounded array");
+    };
+
+  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
+  /// copy-initialization, and a temporary object would be bound to
+  /// the reference, false otherwise.
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    struct reference_converts_from_temporary
+    : public bool_constant<__reference_converts_from_temporary(_Tp, _Up)>
+    {
+      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
+		    && std::__is_complete_or_unbounded(__type_identity<_Up>{}),
+	"template argument must be a complete class or an unbounded array");
+    };
+
+  /// @ingroup variable_templates
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    inline constexpr bool reference_constructs_from_temporary_v
+      = reference_constructs_from_temporary<_Tp, _Up>::value;
+
+  /// @ingroup variable_templates
+  /// @since C++23
+  template<typename _Tp, typename _Up>
+    inline constexpr bool reference_converts_from_temporary_v
+      = reference_converts_from_temporary<_Tp, _Up>::value;
 #endif // C++23
 
 #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
index 22280e1a349..5edca2f3007 100644
--- a/libstdc++-v3/include/std/version
+++ b/libstdc++-v3/include/std/version
@@ -300,10 +300,11 @@
 #endif
 
 #if __cplusplus > 202002L
-// c++2b
+// c++23
 #define __cpp_lib_byteswap 202110L
 #define __cpp_lib_constexpr_typeinfo 202106L
 #define __cpp_lib_is_scoped_enum 202011L
+#define __cpp_lib_reference_from_temporary 202202L
 
 #if _GLIBCXX_HOSTED
 #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
@@ -335,7 +336,7 @@
 #define __cpp_lib_to_underlying 202102L
 #define __cpp_lib_unreachable 202202L
 #endif
-#endif // C++2b
+#endif // C++23
 #endif // C++20
 #endif // C++17
 #endif // C++14
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
new file mode 100644
index 00000000000..2f62e54d46d
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
@@ -0,0 +1,110 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <type_traits>
+#include <testsuite_tr1.h>
+
+#ifndef __cpp_lib_reference_from_temporary
+# error "Feature test macro for reference_from_temporary is missing in <version>"
+#elif __cpp_lib_reference_from_temporary < 202202L
+# error "Feature test macro for reference_from_temporary has wrong value in <version>"
+#endif
+
+void test01()
+{
+  using std::reference_constructs_from_temporary;
+  using std::reference_converts_from_temporary;
+  using namespace __gnu_test;
+
+  struct A { A(); };
+
+  struct B {
+    operator int();
+    explicit operator int&&();
+  };
+
+  struct C {
+    operator int();
+    explicit operator int&();
+  };
+
+  static_assert(test_property<reference_constructs_from_temporary, int, int>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, const volatile void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, void, void>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, long&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, long&&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, long&&>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const A&, A>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, const A&, A&&>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, A&&, A>(true), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&, int[]>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, int[]>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, int&&, int[]>(false), "");
+
+  static_assert(test_property<reference_converts_from_temporary, int, int>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, const volatile void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, void, void>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, long&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, long&&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, long&&>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const A&, A>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const A&, A&&>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, A&&, A>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, int&, int[]>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, int[]>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, int[]>(false), "");
+
+  static_assert(test_property<reference_constructs_from_temporary, int&&, B>(false), "");
+  static_assert(test_property<reference_constructs_from_temporary, const int&, C>(false), "");
+  static_assert(test_property<reference_converts_from_temporary, int&&, B>(true), "");
+  static_assert(test_property<reference_converts_from_temporary, const int&, C>(true), "");
+}
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
new file mode 100644
index 00000000000..65770754299
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <type_traits>
+#include <string>
+
+void test01()
+{
+  static_assert(std::reference_converts_from_temporary_v<const std::string&, const char*>);
+  static_assert(std::reference_constructs_from_temporary_v<const std::string&, const char*>);
+}
diff --git a/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
new file mode 100644
index 00000000000..f56e7c0dabc
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2022 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <version>
+
+#ifndef __cpp_lib_reference_from_temporary
+# error "Feature test macro for reference_from_temporary is missing in <version>"
+#elif __cpp_lib_reference_from_temporary < 202202L
+# error "Feature test macro for reference_from_temporary has wrong value in <version>"
+#endif
diff --git a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
index 9b3957f7d47..2b03ad7067d 100644
--- a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
+++ b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
@@ -346,3 +346,17 @@ static_assert(disjunction_v<false_type, false_type,
               true_type>, "");
 static_assert(!disjunction_v<false_type, false_type,
               false_type>, "");
+#if __cpp_lib_reference_from_temporary >= 202202L
+static_assert(std::reference_converts_from_temporary_v<int&&, int>
+	      && std::reference_converts_from_temporary_v<const int&, int>
+	      && !std::reference_converts_from_temporary_v<int&&, int&&>
+	      && !std::reference_converts_from_temporary_v<const int&, int&&>
+	      && std::reference_converts_from_temporary_v<int&&, long&&>
+	      && std::reference_converts_from_temporary_v<int&&, long>, "");
+static_assert(std::reference_constructs_from_temporary_v<int&&, int>
+	      && std::reference_constructs_from_temporary_v<const int&, int>
+	      && !std::reference_constructs_from_temporary_v<int&&, int&&>
+	      && !std::reference_constructs_from_temporary_v<const int&, int&&>
+	      && std::reference_constructs_from_temporary_v<int&&, long&&>
+	      && std::reference_constructs_from_temporary_v<int&&, long>, "");
+#endif

base-commit: 23dd41c480fa9f06c33c1e6090bbae53869f85af
-- 
2.36.1


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477]
  2022-07-15 15:32         ` Marek Polacek
@ 2022-07-15 19:59           ` Ville Voutilainen
  2022-07-15 20:25             ` [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
  0 siblings, 1 reply; 15+ messages in thread
From: Ville Voutilainen @ 2022-07-15 19:59 UTC (permalink / raw)
  To: Marek Polacek; +Cc: Jason Merrill, Jonathan Wakely, libstdc++, GCC Patches

Well, is_xible is not is_xible_p because it doesn't need to be both is_*
and *_p. But xes_from_temporary is less obviously a question, so
xes_from_temporary_p would imho be a better name.

On Fri, Jul 15, 2022, 18:33 Marek Polacek via Libstdc++ <
libstdc++@gcc.gnu.org> wrote:

> On Thu, Jul 14, 2022 at 11:48:51PM -0400, Jason Merrill wrote:
> > On 7/14/22 13:43, Marek Polacek wrote:
> > > On Tue, Jul 12, 2022 at 04:15:00PM -0400, Jason Merrill wrote:
> > > > On 7/12/22 16:10, Jason Merrill wrote:
> > > > > On 7/8/22 13:41, Marek Polacek wrote:
> > > > > > This patch implements C++23 P2255R2, which adds two new type
> traits to
> > > > > > detect reference binding to a temporary.  They can be used to
> detect code
> > > > > > like
> > > > > >
> > > > > >     std::tuple<const std::string&> t("meow");
> > > > > >
> > > > > > which is incorrect because it always creates a dangling
> reference,
> > > > > > because
> > > > > > the std::string temporary is created inside the selected
> constructor of
> > > > > > std::tuple, and not outside it.
> > > > > >
> > > > > > There are two new compiler builtins,
> > > > > > __reference_constructs_from_temporary
> > > > > > and __reference_converts_from_temporary.  The former is used to
> simulate
> > > > > > direct- and the latter copy-initialization context.  But I had a
> > > > > > hard time
> > > > > > finding a test where there's actually a difference.  Under DR
> 2267, both
> > > > > > of these are invalid:
> > > > > >
> > > > > >     struct A { } a;
> > > > > >     struct B { explicit B(const A&); };
> > > > > >     const B &b1{a};
> > > > > >     const B &b2(a);
> > > > > >
> > > > > > so I had to peruse [over.match.ref], and eventually realized
> that the
> > > > > > difference can be seen here:
> > > > > >
> > > > > >     struct G {
> > > > > >       operator int(); // #1
> > > > > >       explicit operator int&&(); // #2
> > > > > >     };
> > > > > >
> > > > > > int&& r1(G{}); // use #2 (no temporary)
> > > > > > int&& r2 = G{}; // use #1 (a temporary is created to be bound to
> int&&)
> > > > > >
> > > > > > The implementation itself was rather straightforward because we
> already
> > > > > > have conv_binds_ref_to_prvalue.  The main function here is
> > > > > > reference_from_temporary.  The renaming to
> ref_conv_binds_to_temporary_p
> > > > > > is because previously the function didn't distinguish between an
> invalid
> > > > > > conversion and one that binds to a prvalue.
> > > > > >
> > > > > > The patch also adds the relevant class and variable templates to
> > > > > > <type_traits>.
> > > > > >
> > > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > > > >
> > > > > >      PR c++/104477
> > > > > >
> > > > > > gcc/c-family/ChangeLog:
> > > > > >
> > > > > >      * c-common.cc (c_common_reswords): Add
> > > > > >      __reference_constructs_from_temporary and
> > > > > >      __reference_converts_from_temporary.
> > > > > >      * c-common.h (enum rid): Add
> RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > >      RID_REF_CONVERTS_FROM_TEMPORARY.
> > > > > >
> > > > > > gcc/cp/ChangeLog:
> > > > > >
> > > > > >      * call.cc (ref_conv_binds_directly_p): Rename to ...
> > > > > >      (ref_conv_binds_to_temporary_p): ... this.  Add a new bool
> > > > > >      parameter.  Return true only if the conversion is valid and
> > > > > >      conv_binds_ref_to_prvalue returns true.
> > > > > >      * constraint.cc (diagnose_trait_expr): Handle
> > > > > >      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > > > > >      * cp-tree.h (enum cp_trait_kind): Add
> > > > > > CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
> > > > > >      and CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > > > > >      (ref_conv_binds_directly_p): Rename to ...
> > > > > >      (ref_conv_binds_to_temporary_p): ... this.
> > > > > >      (reference_from_temporary): Declare.
> > > > > >      * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
> > > > > >      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > > > > >      * method.cc (reference_from_temporary): New.
> > > > > >      * parser.cc (cp_parser_primary_expression): Handle
> > > > > >      RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > > RID_REF_CONVERTS_FROM_TEMPORARY.
> > > > > >      (cp_parser_trait_expr): Likewise.
> > > > > >      (warn_for_range_copy): Adjust to call
> ref_conv_binds_to_temporary_p.
> > > > > >      * semantics.cc (trait_expr_value): Handle
> > > > > >      CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > > > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > > > > >      (finish_trait_expr): Likewise.
> > > > > >
> > > > > > libstdc++-v3/ChangeLog:
> > > > > >
> > > > > >      * include/std/type_traits
> (reference_constructs_from_temporary,
> > > > > >      reference_converts_from_temporary): New class templates.
> > > > > >      (reference_constructs_from_temporary_v,
> > > > > >      reference_converts_from_temporary_v): New variable
> templates.
> > > > > >      (__cpp_lib_reference_from_temporary): Define for C++23.
> > > > > >      * include/std/version (__cpp_lib_reference_from_temporary):
> > > > > > Define for
> > > > > >      C++23.
> > > > > >      * testsuite/20_util/variable_templates_for_traits.cc: Test
> > > > > >      reference_constructs_from_temporary_v and
> > > > > >      reference_converts_from_temporary_v.
> > > > > >      * testsuite/20_util/reference_from_temporary/value.cc: New
> test.
> > > > > >      * testsuite/20_util/reference_from_temporary/value2.cc: New
> test.
> > > > > >      * testsuite/20_util/reference_from_temporary/version.cc:
> New test.
> > > > > >
> > > > > > gcc/testsuite/ChangeLog:
> > > > > >
> > > > > >      * g++.dg/ext/reference_constructs_from_temporary1.C: New
> test.
> > > > > >      * g++.dg/ext/reference_converts_from_temporary1.C: New test.
> > > > > > ---
> > > > > >    gcc/c-family/c-common.cc                      |   4 +
> > > > > >    gcc/c-family/c-common.h                       |   2 +
> > > > > >    gcc/cp/call.cc                                |  14 +-
> > > > > >    gcc/cp/constraint.cc                          |   8 +
> > > > > >    gcc/cp/cp-tree.h                              |   7 +-
> > > > > >    gcc/cp/cxx-pretty-print.cc                    |   6 +
> > > > > >    gcc/cp/method.cc                              |  28 +++
> > > > > >    gcc/cp/parser.cc                              |  14 +-
> > > > > >    gcc/cp/semantics.cc                           |   8 +
> > > > > >    .../reference_constructs_from_temporary1.C    | 214
> ++++++++++++++++++
> > > > > >    .../ext/reference_converts_from_temporary1.C  | 214
> ++++++++++++++++++
> > > > > >    libstdc++-v3/include/std/type_traits          |  39 ++++
> > > > > >    libstdc++-v3/include/std/version              |   5 +-
> > > > > >    .../20_util/reference_from_temporary/value.cc | 110 +++++++++
> > > > > >    .../reference_from_temporary/value2.cc        |  28 +++
> > > > > >    .../reference_from_temporary/version.cc       |  27 +++
> > > > > >    .../20_util/variable_templates_for_traits.cc  |  14 ++
> > > > > >    17 files changed, 730 insertions(+), 12 deletions(-)
> > > > > >    create mode 100644
> > > > > > gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> > > > > >    create mode 100644
> > > > > > gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> > > > > >    create mode 100644
> > > > > > libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> > > > > >    create mode 100644
> > > > > > libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> > > > > >    create mode 100644
> > > > > >
> libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> > > > > >
> > > > > > diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> > > > > > index 1b8e73f7bc5..655c3aefee6 100644
> > > > > > --- a/gcc/c-family/c-common.cc
> > > > > > +++ b/gcc/c-family/c-common.cc
> > > > > > @@ -537,6 +537,10 @@ const struct c_common_resword
> c_common_reswords[] =
> > > > > >      { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
> > > > > >      { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE,
> D_CXXONLY },
> > > > > >      { "__is_nothrow_constructible",
> RID_IS_NOTHROW_CONSTRUCTIBLE,
> > > > > > D_CXXONLY },
> > > > > > +  { "__reference_constructs_from_temporary",
> > > > > > RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > > > > +                    D_CXXONLY },
> > > > > > +  { "__reference_converts_from_temporary",
> > > > > > RID_REF_CONVERTS_FROM_TEMPORARY,
> > > > > > +                    D_CXXONLY },
> > > > > >      /* C++ transactional memory.  */
> > > > > >      { "synchronized",    RID_SYNCHRONIZED, D_CXX_OBJC |
> D_TRANSMEM },
> > > > > > diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> > > > > > index c0900848965..f9064393b4e 100644
> > > > > > --- a/gcc/c-family/c-common.h
> > > > > > +++ b/gcc/c-family/c-common.h
> > > > > > @@ -184,6 +184,8 @@ enum rid
> > > > > >      RID_IS_UNION,                RID_UNDERLYING_TYPE,
> > > > > >      RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
> > > > > >      RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
> > > > > > +  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > > > > +  RID_REF_CONVERTS_FROM_TEMPORARY,
> > > > > >      /* C++11 */
> > > > > >      RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR,
> > > > > > RID_STATIC_ASSERT,
> > > > > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> > > > > > index fc98552fda2..1ba209f61f1 100644
> > > > > > --- a/gcc/cp/call.cc
> > > > > > +++ b/gcc/cp/call.cc
> > > > > > @@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
> > > > > >      return conv_is_prvalue (next_conversion (c));
> > > > > >    }
> > > > > > -/* True iff converting EXPR to a reference type TYPE does not
> involve
> > > > > > -   creating a temporary.  */
> > > > > > +/* True iff converting EXPR to a reference type TYPE binds the
> > > > > > reference to
> > > > > > +   a temporary.  DIRECT_INIT_P says whether the conversion
> should
> > > > > > be done
> > > > > > +   in direct- or copy-initialization context.  */
> > > > > >    bool
> > > > > > -ref_conv_binds_directly_p (tree type, tree expr)
> > > > > > +ref_conv_binds_to_temporary_p (tree type, tree expr,
> > > > > > +                   bool direct_init_p /*= false*/)
> > > > > >    {
> > > > > >      gcc_assert (TYPE_REF_P (type));
> > > > > >      /* Get the high-water mark for the CONVERSION_OBSTACK.  */
> > > > > >      void *p = conversion_obstack_alloc (0);
> > > > > > +  const int flags = direct_init_p ? LOOKUP_NORMAL :
> LOOKUP_IMPLICIT;
> > > > > >      conversion *conv = implicit_conversion (type, TREE_TYPE
> (expr), expr,
> > > > > > -                      /*c_cast_p=*/false,
> > > > > > -                      LOOKUP_IMPLICIT, tf_none);
> > > > > > -  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue
> (conv);
> > > > > > +                      /*c_cast_p=*/false, flags, tf_none);
> > > > > > +  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue
> (conv);
> > > > > >      /* Free all the conversions we allocated.  */
> > > > > >      obstack_free (&conversion_obstack, p);
> > > > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > > > index 591155cee22..648cc9d176d 100644
> > > > > > --- a/gcc/cp/constraint.cc
> > > > > > +++ b/gcc/cp/constraint.cc
> > > > > > @@ -3687,6 +3687,14 @@ diagnose_trait_expr (tree expr, tree args)
> > > > > >        case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
> > > > > >          inform (loc, "  %qT does not have unique object
> > > > > > representations", t1);
> > > > > >          break;
> > > > > > +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > > +      inform (loc, "  %qT is not a reference that binds to a
> temporary "
> > > > > > +          "object of type %qT (direct-initialization)", t1, t2);
> > > > > > +      break;
> > > > > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > > > > +      inform (loc, "  %qT is not a reference that binds to a
> temporary "
> > > > > > +          "object of type %qT (copy-initialization)", t1, t2);
> > > > > > +      break;
> > > > > >        case CPTK_BASES:
> > > > > >        case CPTK_DIRECT_BASES:
> > > > > >        case CPTK_UNDERLYING_TYPE:
> > > > > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > > > > > index 2fde4f83b41..c3bed31e455 100644
> > > > > > --- a/gcc/cp/cp-tree.h
> > > > > > +++ b/gcc/cp/cp-tree.h
> > > > > > @@ -1397,7 +1397,9 @@ enum cp_trait_kind
> > > > > >      CPTK_IS_ASSIGNABLE,
> > > > > >      CPTK_IS_CONSTRUCTIBLE,
> > > > > >      CPTK_IS_NOTHROW_ASSIGNABLE,
> > > > > > -  CPTK_IS_NOTHROW_CONSTRUCTIBLE
> > > > > > +  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
> > > > > > +  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > > > > +  CPTK_REF_CONVERTS_FROM_TEMPORARY
> > > > > >    };
> > > > > >    /* The types that we are processing.  */
> > > > > > @@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p
> > > > > > (const_tree);
> > > > > >    extern tree type_decays_to            (tree);
> > > > > >    extern tree extract_call_expr            (tree);
> > > > > >    extern tree build_trivial_dtor_call        (tree, bool =
> false);
> > > > > > -extern bool ref_conv_binds_directly_p        (tree, tree);
> > > > > > +extern bool ref_conv_binds_to_temporary_p    (tree, tree, bool
> = false);
> > > > > >    extern tree build_user_type_conversion        (tree, tree,
> int,
> > > > > >                             tsubst_flags_t);
> > > > > >    extern tree build_new_function_call        (tree, vec<tree,
> va_gc> **,
> > > > > > @@ -7105,6 +7107,7 @@ extern tree forward_parm            (tree);
> > > > > >    extern bool is_trivially_xible            (enum tree_code,
> tree, tree);
> > > > > >    extern bool is_nothrow_xible            (enum tree_code,
> tree, tree);
> > > > > >    extern bool is_xible                (enum tree_code, tree,
> tree);
> > > > > > +extern bool reference_from_temporary        (tree, tree, bool);
> > > > > >    extern tree get_defaulted_eh_spec        (tree,
> tsubst_flags_t =
> > > > > > tf_warning_or_error);
> > > > > >    extern bool maybe_explain_implicit_delete    (tree);
> > > > > >    extern void explain_implicit_non_constexpr    (tree);
> > > > > > diff --git a/gcc/cp/cxx-pretty-print.cc
> b/gcc/cp/cxx-pretty-print.cc
> > > > > > index 7e4db2e413b..44590830a61 100644
> > > > > > --- a/gcc/cp/cxx-pretty-print.cc
> > > > > > +++ b/gcc/cp/cxx-pretty-print.cc
> > > > > > @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression
> (cxx_pretty_printer
> > > > > > *pp, tree t)
> > > > > >        case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
> > > > > >          pp_cxx_ws_string (pp, "__is_nothrow_constructible");
> > > > > >          break;
> > > > > > +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > > +      pp_cxx_ws_string (pp,
> "__reference_constructs_from_temporary");
> > > > > > +      break;
> > > > > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > > > > +      pp_cxx_ws_string (pp,
> "__reference_converts_from_temporary");
> > > > > > +      break;
> > > > > >        default:
> > > > > >          gcc_unreachable ();
> > > > > > diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> > > > > > index 0dffd648b0b..dd9715b6725 100644
> > > > > > --- a/gcc/cp/method.cc
> > > > > > +++ b/gcc/cp/method.cc
> > > > > > @@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to,
> tree from)
> > > > > >      return !!expr;
> > > > > >    }
> > > > > > +/* Return true iff conjunction_v<is_reference<T>,
> > > > > > is_constructible<T, U>> is
> > > > > > +   true, and the initialization
> > > > > > +     T t(VAL<U>); // DIRECT_INIT_P
> > > > > > +   or
> > > > > > +     T t = VAL<U>; // !DIRECT_INIT_P
> > > > > > +   binds t to a temporary object whose lifetime is extended.
> > > > > > +   VAL<T> is defined in [meta.unary.prop]:
> > > > > > +   -- If T is a reference or function type, VAL<T> is an
> expression
> > > > > > with the
> > > > > > +   same type and value category as declval<T>().
> > > > > > +   -- Otherwise, VAL<T> is a prvalue that initially has type
> T.   */
> > > > > > +
> > > > > > +bool
> > > > > > +reference_from_temporary (tree to, tree from, bool
> direct_init_p)
> > > > > > +{
> > > > > > +  /* Check is_reference<T>.  */
> > > > > > +  if (!TYPE_REF_P (to))
> > > > > > +    return false;
> > > > > > +  /* Check is_constructible<T, U>.
> > > > > > +     ??? This check doesn't seem to be necessary; if T isn't
> > > > > > constructible
> > > > > > +     from U, we won't be able to create a conversion.  */
> > > > > > +  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE,
> from)))
> > > > > > +    return false;
> > > > >
> > > > > I agree with the comment, did you try leaving this out?  If it
> stays I'd
> > > > > think it needs to consider direct_init_p.
> > > > >
> > > > > > +  tree val = build_stub_object (from);
> > > > > > +  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
> > > > > > +    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) :
> > > > > > rvalue (val);
> > > > > > +  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
> > > > > > +}
> > > > > > +
> > > > > >    /* Categorize various special_function_kinds.  */
> > > > > >    #define SFK_CTOR_P(sfk) \
> > > > > >      ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
> > > > > > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> > > > > > index bf9ea3685f8..edee94bda13 100644
> > > > > > --- a/gcc/cp/parser.cc
> > > > > > +++ b/gcc/cp/parser.cc
> > > > > > @@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser
> *parser,
> > > > > >        case RID_IS_CONSTRUCTIBLE:
> > > > > >        case RID_IS_NOTHROW_ASSIGNABLE:
> > > > > >        case RID_IS_NOTHROW_CONSTRUCTIBLE:
> > > > > > +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > > +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> > > > > >          return cp_parser_trait_expr (parser, token->keyword);
> > > > > >        // C++ concepts
> > > > > > @@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser,
> > > > > > enum rid keyword)
> > > > > >          kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
> > > > > >          variadic = true;
> > > > > >          break;
> > > > > > +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > > +      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
> > > > > > +      binary = true;
> > > > > > +      break;
> > > > > > +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> > > > > > +      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
> > > > > > +      binary = true;
> > > > > > +      break;
> > > > > >        default:
> > > > > >          gcc_unreachable ();
> > > > > >        }
> > > > > > @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree
> expr)
> > > > > >      if (TYPE_REF_P (type))
> > > > > >        {
> > > > > > -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type,
> expr))
> > > > > > +      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p
> (type,
> > > > > > expr))
> > > > > >        {
> > > > > >          auto_diagnostic_group d;
> > > > > >          if (warning_at (loc, OPT_Wrange_loop_construct,
> > > > > > @@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree
> expr)
> > > > > >      tree rtype = cp_build_reference_type (type, /*rval*/false);
> > > > > >      /* If we could initialize the reference directly, it
> wouldn't
> > > > > > involve any
> > > > > >         copies.  */
> > > > > > -  if (!ref_conv_binds_directly_p (rtype, expr))
> > > > > > +  if (ref_conv_binds_to_temporary_p (rtype, expr))
> > > > > >        return;
> > > > >
> > > > > I think this case wants the old handling of invalid conversions you
> > > > > mentioned in your intro; we don't want to suggest changing to a
> > > > > reference if that's ill-formed.
> > > > >
> > > > > In passing we might change the comment to "If we can initialize a
> > > > > reference directly, suggest that to avoid the copy." and move it
> above
> > > > > the rtype declaration.
> > > >
> > > > Hmm, and I suspect we get false positives when expr is a prvalue, so
> > > > initializing a non-reference variable of the same cv-unqualified type
> > > > involves no extra copy?
> > >
> > > I couldn't provoke a false positive here.  Note that
> ref_conv_binds_to_temporary_p
> > > always gets a reference so copy elision isn't applicable here.
> >
> > Ah, I see: if expr is a prvalue, that counts as binding a reference to a
> > temporary, so we (properly) don't think the reference is an improvement.
> >
> > The function name reference_from_temporary seems to me to suggest a tree
> > value rather than bool, maybe rename to ref_xes_from_temporary?
>
> I agree that that's a better name, so I've changed it.  I would've
> preferred
> ref_xes_from_temporary_p but is_xible et al don't have the _p.
>
> I'm going to push this, thanks!
>
> Tested x86_64-pc-linux-gnu, applying to trunk.
>
> -- >8 --
> This patch implements C++23 P2255R2, which adds two new type traits to
> detect reference binding to a temporary.  They can be used to detect code
> like
>
>   std::tuple<const std::string&> t("meow");
>
> which is incorrect because it always creates a dangling reference, because
> the std::string temporary is created inside the selected constructor of
> std::tuple, and not outside it.
>
> There are two new compiler builtins, __reference_constructs_from_temporary
> and __reference_converts_from_temporary.  The former is used to simulate
> direct- and the latter copy-initialization context.  But I had a hard time
> finding a test where there's actually a difference.  Under DR 2267, both
> of these are invalid:
>
>   struct A { } a;
>   struct B { explicit B(const A&); };
>   const B &b1{a};
>   const B &b2(a);
>
> so I had to peruse [over.match.ref], and eventually realized that the
> difference can be seen here:
>
>   struct G {
>     operator int(); // #1
>     explicit operator int&&(); // #2
>   };
>
> int&& r1(G{}); // use #2 (no temporary)
> int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)
>
> The implementation itself was rather straightforward because we already
> have the conv_binds_ref_to_prvalue function.  The main function here is
> ref_xes_from_temporary.
> I've changed the return type of ref_conv_binds_directly to tristate,
> because
> previously the function didn't distinguish between an invalid conversion
> and
> one that binds to a prvalue.  Since it no longer returns a bool, I removed
> the _p suffix.
>
> The patch also adds the relevant class and variable templates to
> <type_traits>.
>
>         PR c++/104477
>
> gcc/c-family/ChangeLog:
>
>         * c-common.cc (c_common_reswords): Add
>         __reference_constructs_from_temporary and
>         __reference_converts_from_temporary.
>         * c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
>         RID_REF_CONVERTS_FROM_TEMPORARY.
>
> gcc/cp/ChangeLog:
>
>         * call.cc (ref_conv_binds_directly_p): Rename to ...
>         (ref_conv_binds_directly): ... this.  Add a new bool parameter.
> Change
>         the return type to tristate.
>         * constraint.cc (diagnose_trait_expr): Handle
>         CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>         * cp-tree.h: Include "tristate.h".
>         (enum cp_trait_kind): Add CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
>         and CPTK_REF_CONVERTS_FROM_TEMPORARY.
>         (ref_conv_binds_directly_p): Rename to ...
>         (ref_conv_binds_directly): ... this.
>         (ref_xes_from_temporary): Declare.
>         * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
>         CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>         * method.cc (ref_xes_from_temporary): New.
>         * parser.cc (cp_parser_primary_expression): Handle
>         RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> RID_REF_CONVERTS_FROM_TEMPORARY.
>         (cp_parser_trait_expr): Likewise.
>         (warn_for_range_copy): Adjust to call ref_conv_binds_directly.
>         * semantics.cc (trait_expr_value): Handle
>         CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> CPTK_REF_CONVERTS_FROM_TEMPORARY.
>         (finish_trait_expr): Likewise.
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/type_traits (reference_constructs_from_temporary,
>         reference_converts_from_temporary): New class templates.
>         (reference_constructs_from_temporary_v,
>         reference_converts_from_temporary_v): New variable templates.
>         (__cpp_lib_reference_from_temporary): Define for C++23.
>         * include/std/version (__cpp_lib_reference_from_temporary): Define
> for
>         C++23.
>         * testsuite/20_util/variable_templates_for_traits.cc: Test
>         reference_constructs_from_temporary_v and
>         reference_converts_from_temporary_v.
>         * testsuite/20_util/reference_from_temporary/value.cc: New test.
>         * testsuite/20_util/reference_from_temporary/value2.cc: New test.
>         * testsuite/20_util/reference_from_temporary/version.cc: New test.
>
> gcc/testsuite/ChangeLog:
>
>         * g++.dg/ext/reference_constructs_from_temporary1.C: New test.
>         * g++.dg/ext/reference_converts_from_temporary1.C: New test.
> ---
>  gcc/c-family/c-common.cc                      |   4 +
>  gcc/c-family/c-common.h                       |   2 +
>  gcc/cp/call.cc                                |  20 +-
>  gcc/cp/constraint.cc                          |   8 +
>  gcc/cp/cp-tree.h                              |   8 +-
>  gcc/cp/cxx-pretty-print.cc                    |   6 +
>  gcc/cp/method.cc                              |  25 ++
>  gcc/cp/parser.cc                              |  36 +--
>  gcc/cp/semantics.cc                           |   8 +
>  .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
>  .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
>  libstdc++-v3/include/std/type_traits          |  39 ++++
>  libstdc++-v3/include/std/version              |   5 +-
>  .../20_util/reference_from_temporary/value.cc | 110 +++++++++
>  .../reference_from_temporary/value2.cc        |  28 +++
>  .../reference_from_temporary/version.cc       |  27 +++
>  .../20_util/variable_templates_for_traits.cc  |  14 ++
>  17 files changed, 744 insertions(+), 24 deletions(-)
>  create mode 100644
> gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
>  create mode 100644
> gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
>  create mode 100644
> libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
>  create mode 100644
> libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
>  create mode 100644
> libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
>
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index 1b8e73f7bc5..655c3aefee6 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -537,6 +537,10 @@ const struct c_common_resword c_common_reswords[] =
>    { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
>    { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
>    { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY
> },
> +  { "__reference_constructs_from_temporary",
> RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> +                                       D_CXXONLY },
> +  { "__reference_converts_from_temporary",
> RID_REF_CONVERTS_FROM_TEMPORARY,
> +                                       D_CXXONLY },
>
>    /* C++ transactional memory.  */
>    { "synchronized",    RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index c0900848965..f9064393b4e 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -184,6 +184,8 @@ enum rid
>    RID_IS_UNION,                RID_UNDERLYING_TYPE,
>    RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
>    RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
> +  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> +  RID_REF_CONVERTS_FROM_TEMPORARY,
>
>    /* C++11 */
>    RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR,
> RID_STATIC_ASSERT,
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index fc98552fda2..191c68cdcfd 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -9109,21 +9109,27 @@ conv_binds_ref_to_prvalue (conversion *c)
>    return conv_is_prvalue (next_conversion (c));
>  }
>
> -/* True iff converting EXPR to a reference type TYPE does not involve
> -   creating a temporary.  */
> +/* Return tristate::TS_TRUE if converting EXPR to a reference type TYPE
> does
> +   not involve creating a temporary.  Return tristate::TS_FALSE if
> converting
> +   EXPR to a reference type TYPE binds the reference to a temporary.  If
> the
> +   conversion is invalid or bad, return tristate::TS_UNKNOWN.
> DIRECT_INIT_P
> +   says whether the conversion should be done in direct- or
> copy-initialization
> +   context.  */
>
> -bool
> -ref_conv_binds_directly_p (tree type, tree expr)
> +tristate
> +ref_conv_binds_directly (tree type, tree expr, bool direct_init_p /*=
> false*/)
>  {
>    gcc_assert (TYPE_REF_P (type));
>
>    /* Get the high-water mark for the CONVERSION_OBSTACK.  */
>    void *p = conversion_obstack_alloc (0);
>
> +  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
>    conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
> -                                         /*c_cast_p=*/false,
> -                                         LOOKUP_IMPLICIT, tf_none);
> -  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
> +                                         /*c_cast_p=*/false, flags,
> tf_none);
> +  tristate ret (tristate::TS_UNKNOWN);
> +  if (conv && !conv->bad_p)
> +    ret = tristate (!conv_binds_ref_to_prvalue (conv));
>
>    /* Free all the conversions we allocated.  */
>    obstack_free (&conversion_obstack, p);
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index f2137eb7aa1..568318f0ba1 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -3697,6 +3697,14 @@ diagnose_trait_expr (tree expr, tree args)
>      case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
>        inform (loc, "  %qT does not have unique object representations",
> t1);
>        break;
> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> +      inform (loc, "  %qT is not a reference that binds to a temporary "
> +             "object of type %qT (direct-initialization)", t1, t2);
> +      break;
> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> +      inform (loc, "  %qT is not a reference that binds to a temporary "
> +             "object of type %qT (copy-initialization)", t1, t2);
> +      break;
>      case CPTK_BASES:
>      case CPTK_DIRECT_BASES:
>      case CPTK_UNDERLYING_TYPE:
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index bec98aa2ac3..cf51c39dc90 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "tm.h"
>  #include "hard-reg-set.h"
>  #include "function.h"
> +#include "tristate.h"
>
>  /* In order for the format checking to accept the C++ front end
>     diagnostic framework extensions, you must include this file before
> @@ -1397,7 +1398,9 @@ enum cp_trait_kind
>    CPTK_IS_ASSIGNABLE,
>    CPTK_IS_CONSTRUCTIBLE,
>    CPTK_IS_NOTHROW_ASSIGNABLE,
> -  CPTK_IS_NOTHROW_CONSTRUCTIBLE
> +  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
> +  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
> +  CPTK_REF_CONVERTS_FROM_TEMPORARY
>  };
>
>  /* The types that we are processing.  */
> @@ -6520,7 +6523,7 @@ extern bool sufficient_parms_p
> (const_tree);
>  extern tree type_decays_to                     (tree);
>  extern tree extract_call_expr                  (tree);
>  extern tree build_trivial_dtor_call            (tree, bool = false);
> -extern bool ref_conv_binds_directly_p          (tree, tree);
> +extern tristate ref_conv_binds_directly                (tree, tree, bool
> = false);
>  extern tree build_user_type_conversion         (tree, tree, int,
>                                                  tsubst_flags_t);
>  extern tree build_new_function_call            (tree, vec<tree, va_gc> **,
> @@ -7105,6 +7108,7 @@ extern tree forward_parm                  (tree);
>  extern bool is_trivially_xible                 (enum tree_code, tree,
> tree);
>  extern bool is_nothrow_xible                   (enum tree_code, tree,
> tree);
>  extern bool is_xible                           (enum tree_code, tree,
> tree);
> +extern bool ref_xes_from_temporary             (tree, tree, bool);
>  extern tree get_defaulted_eh_spec              (tree, tsubst_flags_t =
> tf_warning_or_error);
>  extern bool maybe_explain_implicit_delete      (tree);
>  extern void explain_implicit_non_constexpr     (tree);
> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> index 7e4db2e413b..44590830a61 100644
> --- a/gcc/cp/cxx-pretty-print.cc
> +++ b/gcc/cp/cxx-pretty-print.cc
> @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp,
> tree t)
>      case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
>        pp_cxx_ws_string (pp, "__is_nothrow_constructible");
>        break;
> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> +      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
> +      break;
> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> +      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
> +      break;
>
>      default:
>        gcc_unreachable ();
> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> index 0dffd648b0b..f2050f6e970 100644
> --- a/gcc/cp/method.cc
> +++ b/gcc/cp/method.cc
> @@ -2211,6 +2211,31 @@ is_xible (enum tree_code code, tree to, tree from)
>    return !!expr;
>  }
>
> +/* Return true iff conjunction_v<is_reference<T>, is_constructible<T, U>>
> is
> +   true, and the initialization
> +     T t(VAL<U>); // DIRECT_INIT_P
> +   or
> +     T t = VAL<U>; // !DIRECT_INIT_P
> +   binds t to a temporary object whose lifetime is extended.
> +   VAL<T> is defined in [meta.unary.prop]:
> +   -- If T is a reference or function type, VAL<T> is an expression with
> the
> +   same type and value category as declval<T>().
> +   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
> +
> +bool
> +ref_xes_from_temporary (tree to, tree from, bool direct_init_p)
> +{
> +  /* Check is_reference<T>.  */
> +  if (!TYPE_REF_P (to))
> +    return false;
> +  /* We don't check is_constructible<T, U>: if T isn't constructible
> +     from U, we won't be able to create a conversion.  */
> +  tree val = build_stub_object (from);
> +  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
> +    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) : rvalue
> (val);
> +  return ref_conv_binds_directly (to, val, direct_init_p).is_false ();
> +}
> +
>  /* Categorize various special_function_kinds.  */
>  #define SFK_CTOR_P(sfk) \
>    ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index bf9ea3685f8..4f67441eeb1 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
>         case RID_IS_CONSTRUCTIBLE:
>         case RID_IS_NOTHROW_ASSIGNABLE:
>         case RID_IS_NOTHROW_CONSTRUCTIBLE:
> +       case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> +       case RID_REF_CONVERTS_FROM_TEMPORARY:
>           return cp_parser_trait_expr (parser, token->keyword);
>
>         // C++ concepts
> @@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser, enum rid
> keyword)
>        kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
>        variadic = true;
>        break;
> +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> +      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
> +      binary = true;
> +      break;
> +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> +      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
> +      binary = true;
> +      break;
>      default:
>        gcc_unreachable ();
>      }
> @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
>
>    if (TYPE_REF_P (type))
>      {
> -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
> +      if (glvalue_p (expr) && ref_conv_binds_directly (type,
> expr).is_false ())
>         {
>           auto_diagnostic_group d;
>           if (warning_at (loc, OPT_Wrange_loop_construct,
> @@ -13839,20 +13849,20 @@ warn_for_range_copy (tree decl, tree expr)
>           && trivially_copyable_p (type)))
>      return;
>
> +  /* If we can initialize a reference directly, suggest that to avoid the
> +     copy.  */
>    tree rtype = cp_build_reference_type (type, /*rval*/false);
> -  /* If we could initialize the reference directly, it wouldn't involve
> any
> -     copies.  */
> -  if (!ref_conv_binds_directly_p (rtype, expr))
> -    return;
> -
> -  auto_diagnostic_group d;
> -  if (warning_at (loc, OPT_Wrange_loop_construct,
> -                 "loop variable %qD creates a copy from type %qT",
> -                 decl, type))
> +  if (ref_conv_binds_directly (rtype, expr).is_true ())
>      {
> -      gcc_rich_location richloc (loc);
> -      richloc.add_fixit_insert_before ("&");
> -      inform (&richloc, "use reference type to prevent copying");
> +      auto_diagnostic_group d;
> +      if (warning_at (loc, OPT_Wrange_loop_construct,
> +                     "loop variable %qD creates a copy from type %qT",
> +                     decl, type))
> +       {
> +         gcc_rich_location richloc (loc);
> +         richloc.add_fixit_insert_before ("&");
> +         inform (&richloc, "use reference type to prevent copying");
> +       }
>      }
>  }
>
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 2344b5eea00..96037c21b85 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -12007,6 +12007,12 @@ trait_expr_value (cp_trait_kind kind, tree type1,
> tree type2)
>      case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
>        return is_nothrow_xible (INIT_EXPR, type1, type2);
>
> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> +      return ref_xes_from_temporary (type1, type2, /*direct_init=*/true);
> +
> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> +      return ref_xes_from_temporary (type1, type2, /*direct_init=*/false);
> +
>      default:
>        gcc_unreachable ();
>        return false;
> @@ -12088,6 +12094,8 @@ finish_trait_expr (location_t loc, cp_trait_kind
> kind, tree type1, tree type2)
>      case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
>      case CPTK_IS_NOTHROW_ASSIGNABLE:
>      case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
> +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
>        if (!check_trait_type (type1)
>           || !check_trait_type (type2))
>         return error_mark_node;
> diff --git
> a/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> new file mode 100644
> index 00000000000..76de905a35d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> @@ -0,0 +1,214 @@
> +// P2255R2
> +// PR c++/104477
> +// { dg-do compile { target c++11 } }
> +
> +#define SA(X) static_assert((X),#X)
> +
> +struct A { A(); };
> +struct B { operator A(); };
> +struct C { explicit C(); };
> +struct D { explicit operator A(); };
> +struct E { explicit operator A&(); };
> +struct F { explicit operator A&&(); };
> +// Could use a class template with explicit(bool), but then this would
> need
> +// C++20.
> +struct G {
> +  operator int();
> +  explicit operator int&&();
> +};
> +struct G2 {
> +  explicit operator int();
> +  operator int&&();
> +};
> +struct H {
> +  operator int();
> +  explicit operator int&();
> +};
> +struct H2 {
> +  explicit operator int();
> +  operator int&();
> +};
> +
> +struct Base { };
> +struct Der : Base { };
> +
> +template<typename T, typename RT>
> +struct morph {
> +  mutable T val{};
> +  operator RT() const { return static_cast<RT>(val); }
> +};
> +
> +template<typename T> using id = T;
> +
> +// Built-in types.
> +SA(!__reference_constructs_from_temporary(int, int));
> +SA(!__reference_constructs_from_temporary(int&, void));
> +SA(!__reference_constructs_from_temporary(int&, const void));
> +SA(!__reference_constructs_from_temporary(int&, volatile void));
> +SA(!__reference_constructs_from_temporary(int&, const volatile void));
> +SA(!__reference_constructs_from_temporary(void, void));
> +SA(!__reference_constructs_from_temporary(void, int));
> +SA(!__reference_constructs_from_temporary(int&, int));
> +SA(!__reference_constructs_from_temporary(int&, int&));
> +SA(!__reference_constructs_from_temporary(int&, int&&));
> +SA(!__reference_constructs_from_temporary(int&, long));
> +// non-const lvalue reference to type 'int' cannot bind to a value of
> unrelated type 'long'
> +SA(!__reference_constructs_from_temporary(int&, long&));
> +SA(!__reference_constructs_from_temporary(int&, long&&));
> +SA( __reference_constructs_from_temporary(const int&, int));
> +SA(!__reference_constructs_from_temporary(const int&, int&));
> +SA(!__reference_constructs_from_temporary(const int&, const int&));
> +SA(!__reference_constructs_from_temporary(const int&, int&&));
> +SA( __reference_constructs_from_temporary(const int&, long));
> +SA( __reference_constructs_from_temporary(const int&, long&));
> +SA( __reference_constructs_from_temporary(const int&, long&&));
> +SA( __reference_constructs_from_temporary(int&&, int));
> +SA(!__reference_constructs_from_temporary(int&&, int&));
> +SA(!__reference_constructs_from_temporary(int&&, int&&));
> +SA( __reference_constructs_from_temporary(int&&, long));
> +SA( __reference_constructs_from_temporary(int&&, long&));
> +SA( __reference_constructs_from_temporary(int&&, long&&));
> +SA(!__reference_constructs_from_temporary(unsigned int&, double));
> +SA(!__reference_constructs_from_temporary(volatile int&, int));
> +SA(!__reference_constructs_from_temporary(const volatile int&, int));
> +SA(!__reference_constructs_from_temporary(volatile int&, int&));
> +SA(!__reference_constructs_from_temporary(const volatile int&, int&));
> +SA(!__reference_constructs_from_temporary(volatile int&, int&&));
> +SA(!__reference_constructs_from_temporary(const volatile int&, int&&));
> +
> +// Classes.
> +SA(!__reference_constructs_from_temporary(A, A));
> +// A& r(A{}); doesn't construct.
> +SA(!__reference_constructs_from_temporary(A&, A));
> +SA(!__reference_constructs_from_temporary(A&, A&));
> +SA(!__reference_constructs_from_temporary(A&, A&&));
> +// Here we get const struct A & r = (const struct A &) &D.2414;
> +SA( __reference_constructs_from_temporary(const A&, A));
> +SA(!__reference_constructs_from_temporary(const A&, A&));
> +SA(!__reference_constructs_from_temporary(const A&, const A&));
> +SA(!__reference_constructs_from_temporary(const A&, A&&));
> +// Here we get struct A & r = (struct A &) &D.2439;
> +SA( __reference_constructs_from_temporary(A&&, A));
> +SA(!__reference_constructs_from_temporary(A&&, A&));
> +SA(!__reference_constructs_from_temporary(A&&, const A&));
> +SA(!__reference_constructs_from_temporary(A&&, A&&));
> +
> +SA(!__reference_constructs_from_temporary(A, B));
> +SA(!__reference_constructs_from_temporary(A&, B));
> +SA(!__reference_constructs_from_temporary(A&, B&));
> +SA(!__reference_constructs_from_temporary(A&, const B&));
> +SA(!__reference_constructs_from_temporary(A&, B&&));
> +SA( __reference_constructs_from_temporary(const A&, B));
> +SA( __reference_constructs_from_temporary(const A&, B&));
> +// Doesn't construct, so it's false.
> +SA(!__reference_constructs_from_temporary(const A&, const B&));
> +SA( __reference_constructs_from_temporary(const A&, B&&));
> +SA( __reference_constructs_from_temporary(A&&, B));
> +SA( __reference_constructs_from_temporary(A&&, B&));
> +SA(!__reference_constructs_from_temporary(A&&, const B&));
> +SA( __reference_constructs_from_temporary(A&&, B&&));
> +
> +SA(!__reference_constructs_from_temporary(const A&, C));
> +SA(!__reference_constructs_from_temporary(const A&, C&));
> +
> +// Doesn't construct, so it's false.
> +SA(!__reference_constructs_from_temporary(int&, morph<int, int>));
> +// Here we get
> +//   const int & r2 = D.2580 = morph<int, int>::operator int
> +//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
> +SA( __reference_constructs_from_temporary(const int&, morph<int, int>));
> +SA(!__reference_constructs_from_temporary(int&, morph<int, int&>));
> +SA(!__reference_constructs_from_temporary(int&, morph<int, const int&>));
> +SA(!__reference_constructs_from_temporary(int&, morph<int, int&&>));
> +SA( __reference_constructs_from_temporary(const int&, morph<long,
> long&>));
> +
> +// These are like const C& c(cref); so the explicit ctor C isn't a problem
> +// even in copy-init context.  const C& r = {}; would be a different
> story.
> +SA(!__reference_constructs_from_temporary(C, C));
> +SA(!__reference_constructs_from_temporary(C&, C));
> +SA(!__reference_constructs_from_temporary(C&, C&));
> +SA(!__reference_constructs_from_temporary(C&, C&&));
> +SA( __reference_constructs_from_temporary(const C&, C));
> +SA(!__reference_constructs_from_temporary(const C&, C&));
> +SA(!__reference_constructs_from_temporary(const C&, const C&));
> +SA(!__reference_constructs_from_temporary(const C&, C&&));
> +SA( __reference_constructs_from_temporary(C&&, C));
> +SA(!__reference_constructs_from_temporary(C&&, C&));
> +SA(!__reference_constructs_from_temporary(C&&, const C&));
> +SA(!__reference_constructs_from_temporary(C&&, C&&));
> +
> +// These are all false ultimately because of CWG 2267, which we implement.
> +SA(!__reference_constructs_from_temporary(A, D));
> +SA(!__reference_constructs_from_temporary(A&, D));
> +SA(!__reference_constructs_from_temporary(A&, D&));
> +SA(!__reference_constructs_from_temporary(A&, const D&));
> +SA(!__reference_constructs_from_temporary(A&, D&&));
> +SA(!__reference_constructs_from_temporary(const A&, D));
> +SA(!__reference_constructs_from_temporary(const A&, D&));
> +SA(!__reference_constructs_from_temporary(const A&, const D&));
> +SA(!__reference_constructs_from_temporary(const A&, D&&));
> +SA(!__reference_constructs_from_temporary(A&&, D));
> +SA(!__reference_constructs_from_temporary(A&&, D&));
> +SA(!__reference_constructs_from_temporary(A&&, const D&));
> +SA(!__reference_constructs_from_temporary(A&&, D&&));
> +
> +SA(!__reference_constructs_from_temporary(A, E));
> +/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
> +   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715,
> {}>)
> +   so we're not binding the reference to a temporary, although there is
> +   a temporary involved.  So the result is false in both copy- and direct-
> +   init, albeit for different reasons!  */
> +SA(!__reference_constructs_from_temporary(A&, E));
> +// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
> +SA(!__reference_constructs_from_temporary(A&, E&));
> +SA(!__reference_constructs_from_temporary(A&, const E&));
> +SA(!__reference_constructs_from_temporary(A&, E&&));
> +// direct-init:
> +// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720,
> {}>)
> +SA(!__reference_constructs_from_temporary(const A&, E));
> +SA(!__reference_constructs_from_temporary(const A&, E&));
> +SA(!__reference_constructs_from_temporary(const A&, const E&));
> +SA(!__reference_constructs_from_temporary(const A&, E&&));
> +SA(!__reference_constructs_from_temporary(A&&, E));
> +SA(!__reference_constructs_from_temporary(A&&, E&));
> +SA(!__reference_constructs_from_temporary(A&&, const E&));
> +SA(!__reference_constructs_from_temporary(A&&, E&&));
> +
> +SA(!__reference_constructs_from_temporary(A, F));
> +// A& a1(F{}); and A& a2 = F{}; both invalid.
> +SA(!__reference_constructs_from_temporary(A&, F));
> +SA(!__reference_constructs_from_temporary(A&, F&));
> +SA(!__reference_constructs_from_temporary(A&, const F&));
> +SA(!__reference_constructs_from_temporary(A&, F&&));
> +SA(!__reference_constructs_from_temporary(const A&, F));
> +SA(!__reference_constructs_from_temporary(const A&, F&));
> +SA(!__reference_constructs_from_temporary(const A&, const F&));
> +SA(!__reference_constructs_from_temporary(const A&, F&&));
> +SA(!__reference_constructs_from_temporary(A&&, F));
> +SA(!__reference_constructs_from_temporary(A&&, F&));
> +SA(!__reference_constructs_from_temporary(A&&, const F&));
> +SA(!__reference_constructs_from_temporary(A&&, F&&));
> +
> +/* This is where _converts_ and _constructs_ will differ:
> +   in direct-init we use G::operator int&& (no temporary),
> +   but in copy-init we use G::operator int, where a temporary is created
> +   to be bound to int&&.  */
> +SA(!__reference_constructs_from_temporary(int&&, G));
> +// Similar to the previous one.
> +SA(!__reference_constructs_from_temporary(const int&, H));
> +/* And here I've switched the explicit-ness.  In both copy- and
> direct-init
> +   we call operator int&, so no temporary.  */
> +SA(!__reference_constructs_from_temporary(int&&, G2));
> +SA(!__reference_constructs_from_temporary(const int&, H2));
> +
> +SA(!__reference_constructs_from_temporary(const Base&, Der));
> +
> +// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
> +SA(!__reference_constructs_from_temporary(int&&, id<int[3]>));
> +
> +// Arrays.
> +SA(!__reference_constructs_from_temporary(int, int[]));
> +SA(!__reference_constructs_from_temporary(int[], int[]));
> +SA(!__reference_constructs_from_temporary(int&, int[]));
> +SA(!__reference_constructs_from_temporary(int&&, int[]));
> +SA(!__reference_constructs_from_temporary(const int&, int[]));
> diff --git a/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> new file mode 100644
> index 00000000000..90196c38742
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> @@ -0,0 +1,214 @@
> +// P2255R2
> +// PR c++/104477
> +// { dg-do compile { target c++11 } }
> +
> +#define SA(X) static_assert((X),#X)
> +
> +struct A { A(); };
> +struct B { operator A(); };
> +struct C { explicit C(); };
> +struct D { explicit operator A(); };
> +struct E { explicit operator A&(); };
> +struct F { explicit operator A&&(); };
> +// Could use a class template with explicit(bool), but then this would
> need
> +// C++20.
> +struct G {
> +  operator int();
> +  explicit operator int&&();
> +};
> +struct G2 {
> +  explicit operator int();
> +  operator int&&();
> +};
> +struct H {
> +  operator int();
> +  explicit operator int&();
> +};
> +struct H2 {
> +  explicit operator int();
> +  operator int&();
> +};
> +
> +struct Base { };
> +struct Der : Base { };
> +
> +template<typename T, typename RT>
> +struct morph {
> +  mutable T val{};
> +  operator RT() const { return static_cast<RT>(val); }
> +};
> +
> +template<typename T> using id = T;
> +
> +// Built-in types.
> +SA(!__reference_converts_from_temporary(int, int));
> +SA(!__reference_converts_from_temporary(int&, void));
> +SA(!__reference_converts_from_temporary(int&, const void));
> +SA(!__reference_converts_from_temporary(int&, volatile void));
> +SA(!__reference_converts_from_temporary(int&, const volatile void));
> +SA(!__reference_converts_from_temporary(void, void));
> +SA(!__reference_converts_from_temporary(void, int));
> +SA(!__reference_converts_from_temporary(int&, int));
> +SA(!__reference_converts_from_temporary(int&, int&));
> +SA(!__reference_converts_from_temporary(int&, int&&));
> +SA(!__reference_converts_from_temporary(int&, long));
> +// non-const lvalue reference to type 'int' cannot bind to a value of
> unrelated type 'long'
> +SA(!__reference_converts_from_temporary(int&, long&));
> +SA(!__reference_converts_from_temporary(int&, long&&));
> +SA( __reference_converts_from_temporary(const int&, int));
> +SA(!__reference_converts_from_temporary(const int&, int&));
> +SA(!__reference_converts_from_temporary(const int&, const int&));
> +SA(!__reference_converts_from_temporary(const int&, int&&));
> +SA( __reference_converts_from_temporary(const int&, long));
> +SA( __reference_converts_from_temporary(const int&, long&));
> +SA( __reference_converts_from_temporary(const int&, long&&));
> +SA( __reference_converts_from_temporary(int&&, int));
> +SA(!__reference_converts_from_temporary(int&&, int&));
> +SA(!__reference_converts_from_temporary(int&&, int&&));
> +SA( __reference_converts_from_temporary(int&&, long));
> +SA( __reference_converts_from_temporary(int&&, long&));
> +SA( __reference_converts_from_temporary(int&&, long&&));
> +SA(!__reference_converts_from_temporary(unsigned int&, double));
> +SA(!__reference_converts_from_temporary(volatile int&, int));
> +SA(!__reference_converts_from_temporary(const volatile int&, int));
> +SA(!__reference_converts_from_temporary(volatile int&, int&));
> +SA(!__reference_converts_from_temporary(const volatile int&, int&));
> +SA(!__reference_converts_from_temporary(volatile int&, int&&));
> +SA(!__reference_converts_from_temporary(const volatile int&, int&&));
> +
> +// Classes.
> +SA(!__reference_converts_from_temporary(A, A));
> +// A& r(A{}); doesn't construct.
> +SA(!__reference_converts_from_temporary(A&, A));
> +SA(!__reference_converts_from_temporary(A&, A&));
> +SA(!__reference_converts_from_temporary(A&, A&&));
> +// Here we get const struct A & r = (const struct A &) &D.2414;
> +SA( __reference_converts_from_temporary(const A&, A));
> +SA(!__reference_converts_from_temporary(const A&, A&));
> +SA(!__reference_converts_from_temporary(const A&, const A&));
> +SA(!__reference_converts_from_temporary(const A&, A&&));
> +// Here we get struct A & r = (struct A &) &D.2439;
> +SA( __reference_converts_from_temporary(A&&, A));
> +SA(!__reference_converts_from_temporary(A&&, A&));
> +SA(!__reference_converts_from_temporary(A&&, const A&));
> +SA(!__reference_converts_from_temporary(A&&, A&&));
> +
> +SA(!__reference_converts_from_temporary(A, B));
> +SA(!__reference_converts_from_temporary(A&, B));
> +SA(!__reference_converts_from_temporary(A&, B&));
> +SA(!__reference_converts_from_temporary(A&, const B&));
> +SA(!__reference_converts_from_temporary(A&, B&&));
> +SA( __reference_converts_from_temporary(const A&, B));
> +SA( __reference_converts_from_temporary(const A&, B&));
> +// Doesn't construct, so it's false.
> +SA(!__reference_converts_from_temporary(const A&, const B&));
> +SA( __reference_converts_from_temporary(const A&, B&&));
> +SA( __reference_converts_from_temporary(A&&, B));
> +SA( __reference_converts_from_temporary(A&&, B&));
> +SA(!__reference_converts_from_temporary(A&&, const B&));
> +SA( __reference_converts_from_temporary(A&&, B&&));
> +
> +SA(!__reference_converts_from_temporary(const A&, C));
> +SA(!__reference_converts_from_temporary(const A&, C&));
> +
> +// Doesn't construct, so it's false.
> +SA(!__reference_converts_from_temporary(int&, morph<int, int>));
> +// Here we get
> +//   const int & r2 = D.2580 = morph<int, int>::operator int
> +//     (&TARGET_EXPR <D.2578, {.val=0}>); (const int &) &D.2580;
> +SA( __reference_converts_from_temporary(const int&, morph<int, int>));
> +SA(!__reference_converts_from_temporary(int&, morph<int, int&>));
> +SA(!__reference_converts_from_temporary(int&, morph<int, const int&>));
> +SA(!__reference_converts_from_temporary(int&, morph<int, int&&>));
> +SA( __reference_converts_from_temporary(const int&, morph<long, long&>));
> +
> +// These are like const C& c(cref); so the explicit ctor C isn't a problem
> +// even in copy-init context.  const C& r = {}; would be a different
> story.
> +SA(!__reference_converts_from_temporary(C, C));
> +SA(!__reference_converts_from_temporary(C&, C));
> +SA(!__reference_converts_from_temporary(C&, C&));
> +SA(!__reference_converts_from_temporary(C&, C&&));
> +SA( __reference_converts_from_temporary(const C&, C));
> +SA(!__reference_converts_from_temporary(const C&, C&));
> +SA(!__reference_converts_from_temporary(const C&, const C&));
> +SA(!__reference_converts_from_temporary(const C&, C&&));
> +SA( __reference_converts_from_temporary(C&&, C));
> +SA(!__reference_converts_from_temporary(C&&, C&));
> +SA(!__reference_converts_from_temporary(C&&, const C&));
> +SA(!__reference_converts_from_temporary(C&&, C&&));
> +
> +// These are all false ultimately because of CWG 2267, which we implement.
> +SA(!__reference_converts_from_temporary(A, D));
> +SA(!__reference_converts_from_temporary(A&, D));
> +SA(!__reference_converts_from_temporary(A&, D&));
> +SA(!__reference_converts_from_temporary(A&, const D&));
> +SA(!__reference_converts_from_temporary(A&, D&&));
> +SA(!__reference_converts_from_temporary(const A&, D));
> +SA(!__reference_converts_from_temporary(const A&, D&));
> +SA(!__reference_converts_from_temporary(const A&, const D&));
> +SA(!__reference_converts_from_temporary(const A&, D&&));
> +SA(!__reference_converts_from_temporary(A&&, D));
> +SA(!__reference_converts_from_temporary(A&&, D&));
> +SA(!__reference_converts_from_temporary(A&&, const D&));
> +SA(!__reference_converts_from_temporary(A&&, D&&));
> +
> +SA(!__reference_converts_from_temporary(A, E));
> +/* A& a1(E{}); compiles, but A& a2 = E{}; doesn't.
> +   With the former, we get A& a = E::operator A& (&TARGET_EXPR <D.2715,
> {}>)
> +   so we're not binding the reference to a temporary, although there is
> +   a temporary involved.  So the result is false in both copy- and direct-
> +   init, albeit for different reasons!  */
> +SA(!__reference_converts_from_temporary(A&, E));
> +// A& a = E::operator A& ((struct E *) r)); copy-init doesn't compile.
> +SA(!__reference_converts_from_temporary(A&, E&));
> +SA(!__reference_converts_from_temporary(A&, const E&));
> +SA(!__reference_converts_from_temporary(A&, E&&));
> +// direct-init:
> +// const A& a = (const struct A &) E::operator A& (&TARGET_EXPR <D.2720,
> {}>)
> +SA(!__reference_converts_from_temporary(const A&, E));
> +SA(!__reference_converts_from_temporary(const A&, E&));
> +SA(!__reference_converts_from_temporary(const A&, const E&));
> +SA(!__reference_converts_from_temporary(const A&, E&&));
> +SA(!__reference_converts_from_temporary(A&&, E));
> +SA(!__reference_converts_from_temporary(A&&, E&));
> +SA(!__reference_converts_from_temporary(A&&, const E&));
> +SA(!__reference_converts_from_temporary(A&&, E&&));
> +
> +SA(!__reference_converts_from_temporary(A, F));
> +// A& a1(F{}); and A& a2 = F{}; both invalid.
> +SA(!__reference_converts_from_temporary(A&, F));
> +SA(!__reference_converts_from_temporary(A&, F&));
> +SA(!__reference_converts_from_temporary(A&, const F&));
> +SA(!__reference_converts_from_temporary(A&, F&&));
> +SA(!__reference_converts_from_temporary(const A&, F));
> +SA(!__reference_converts_from_temporary(const A&, F&));
> +SA(!__reference_converts_from_temporary(const A&, const F&));
> +SA(!__reference_converts_from_temporary(const A&, F&&));
> +SA(!__reference_converts_from_temporary(A&&, F));
> +SA(!__reference_converts_from_temporary(A&&, F&));
> +SA(!__reference_converts_from_temporary(A&&, const F&));
> +SA(!__reference_converts_from_temporary(A&&, F&&));
> +
> +/* This is where _converts_ and _constructs_ will differ:
> +   in direct-init we use G::operator int&& (no temporary),
> +   but in copy-init we use G::operator int, where a temporary is created
> +   to be bound to int&&.  */
> +SA( __reference_converts_from_temporary(int&&, G));
> +// Similar to the previous one.
> +SA( __reference_converts_from_temporary(const int&, H));
> +/* And here I've switched the explicit-ness.  In both copy- and
> direct-init
> +   we call operator int&, so no temporary.  */
> +SA(!__reference_converts_from_temporary(int&&, G2));
> +SA(!__reference_converts_from_temporary(const int&, H2));
> +
> +SA(!__reference_converts_from_temporary(const Base&, Der));
> +
> +// This fails because std::is_constructible_v<int&&, id<int[3]>> is false.
> +SA(!__reference_converts_from_temporary(int&&, id<int[3]>));
> +
> +// Arrays.
> +SA(!__reference_converts_from_temporary(int, int[]));
> +SA(!__reference_converts_from_temporary(int[], int[]));
> +SA(!__reference_converts_from_temporary(int&, int[]));
> +SA(!__reference_converts_from_temporary(int&&, int[]));
> +SA(!__reference_converts_from_temporary(const int&, int[]));
> diff --git a/libstdc++-v3/include/std/type_traits
> b/libstdc++-v3/include/std/type_traits
> index e5f58bc2e3f..b1a1deecf66 100644
> --- a/libstdc++-v3/include/std/type_traits
> +++ b/libstdc++-v3/include/std/type_traits
> @@ -3505,6 +3505,45 @@ template<typename _Ret, typename _Fn, typename...
> _Args>
>    template<typename _Tp>
>      inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
>
> +#define __cpp_lib_reference_from_temporary 202202L
> +
> +  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
> +  /// direct-initialization, and a temporary object would be bound to
> +  /// the reference, false otherwise.
> +  /// @since C++23
> +  template<typename _Tp, typename _Up>
> +    struct reference_constructs_from_temporary
> +    : public bool_constant<__reference_constructs_from_temporary(_Tp,
> _Up)>
> +    {
> +
> static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
> +                   &&
> std::__is_complete_or_unbounded(__type_identity<_Up>{}),
> +       "template argument must be a complete class or an unbounded
> array");
> +    };
> +
> +  /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
> +  /// copy-initialization, and a temporary object would be bound to
> +  /// the reference, false otherwise.
> +  /// @since C++23
> +  template<typename _Tp, typename _Up>
> +    struct reference_converts_from_temporary
> +    : public bool_constant<__reference_converts_from_temporary(_Tp, _Up)>
> +    {
> +
> static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{})
> +                   &&
> std::__is_complete_or_unbounded(__type_identity<_Up>{}),
> +       "template argument must be a complete class or an unbounded
> array");
> +    };
> +
> +  /// @ingroup variable_templates
> +  /// @since C++23
> +  template<typename _Tp, typename _Up>
> +    inline constexpr bool reference_constructs_from_temporary_v
> +      = reference_constructs_from_temporary<_Tp, _Up>::value;
> +
> +  /// @ingroup variable_templates
> +  /// @since C++23
> +  template<typename _Tp, typename _Up>
> +    inline constexpr bool reference_converts_from_temporary_v
> +      = reference_converts_from_temporary<_Tp, _Up>::value;
>  #endif // C++23
>
>  #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
> diff --git a/libstdc++-v3/include/std/version
> b/libstdc++-v3/include/std/version
> index 22280e1a349..5edca2f3007 100644
> --- a/libstdc++-v3/include/std/version
> +++ b/libstdc++-v3/include/std/version
> @@ -300,10 +300,11 @@
>  #endif
>
>  #if __cplusplus > 202002L
> -// c++2b
> +// c++23
>  #define __cpp_lib_byteswap 202110L
>  #define __cpp_lib_constexpr_typeinfo 202106L
>  #define __cpp_lib_is_scoped_enum 202011L
> +#define __cpp_lib_reference_from_temporary 202202L
>
>  #if _GLIBCXX_HOSTED
>  #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
> @@ -335,7 +336,7 @@
>  #define __cpp_lib_to_underlying 202102L
>  #define __cpp_lib_unreachable 202202L
>  #endif
> -#endif // C++2b
> +#endif // C++23
>  #endif // C++20
>  #endif // C++17
>  #endif // C++14
> diff --git
> a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> new file mode 100644
> index 00000000000..2f62e54d46d
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> @@ -0,0 +1,110 @@
> +// Copyright (C) 2022 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library.  This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, or (at your option)
> +// any later version.
> +
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +// GNU General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3.  If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +// { dg-options "-std=gnu++23" }
> +// { dg-do compile { target c++23 } }
> +
> +#include <type_traits>
> +#include <testsuite_tr1.h>
> +
> +#ifndef __cpp_lib_reference_from_temporary
> +# error "Feature test macro for reference_from_temporary is missing in
> <version>"
> +#elif __cpp_lib_reference_from_temporary < 202202L
> +# error "Feature test macro for reference_from_temporary has wrong value
> in <version>"
> +#endif
> +
> +void test01()
> +{
> +  using std::reference_constructs_from_temporary;
> +  using std::reference_converts_from_temporary;
> +  using namespace __gnu_test;
> +
> +  struct A { A(); };
> +
> +  struct B {
> +    operator int();
> +    explicit operator int&&();
> +  };
> +
> +  struct C {
> +    operator int();
> +    explicit operator int&();
> +  };
> +
> +  static_assert(test_property<reference_constructs_from_temporary, int,
> int>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> void>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> const volatile void>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, void,
> void>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> int>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> int&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> int&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> long>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> long&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> long&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> int&, int>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> int&, int&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> int&, int&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> int&, long>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> int&, long&>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> int&, long&&>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&,
> int>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&,
> int&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&,
> int&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&,
> long>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&,
> long&>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&,
> long&&>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> A&, A>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> A&, A&&>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, A&&,
> A>(true), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&,
> int[]>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> int&, int[]>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, int&&,
> int[]>(false), "");
> +
> +  static_assert(test_property<reference_converts_from_temporary, int,
> int>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> void>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> const volatile void>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, void,
> void>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> int>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> int&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> int&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> long>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> long&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> long&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> int&, int>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> int&, int&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> int&, int&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> int&, long>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> int&, long&>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> int&, long&&>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&,
> int>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&,
> int&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&,
> int&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&,
> long>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&,
> long&>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&,
> long&&>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> A&, A>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> A&, A&&>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, A&&,
> A>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&,
> int[]>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> int&, int[]>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&,
> int[]>(false), "");
> +
> +  static_assert(test_property<reference_constructs_from_temporary, int&&,
> B>(false), "");
> +  static_assert(test_property<reference_constructs_from_temporary, const
> int&, C>(false), "");
> +  static_assert(test_property<reference_converts_from_temporary, int&&,
> B>(true), "");
> +  static_assert(test_property<reference_converts_from_temporary, const
> int&, C>(true), "");
> +}
> diff --git
> a/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> new file mode 100644
> index 00000000000..65770754299
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> @@ -0,0 +1,28 @@
> +// Copyright (C) 2022 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library.  This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, or (at your option)
> +// any later version.
> +
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +// GNU General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3.  If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +// { dg-options "-std=gnu++23" }
> +// { dg-do compile { target c++23 } }
> +
> +#include <type_traits>
> +#include <string>
> +
> +void test01()
> +{
> +  static_assert(std::reference_converts_from_temporary_v<const
> std::string&, const char*>);
> +  static_assert(std::reference_constructs_from_temporary_v<const
> std::string&, const char*>);
> +}
> diff --git
> a/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> new file mode 100644
> index 00000000000..f56e7c0dabc
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> @@ -0,0 +1,27 @@
> +// Copyright (C) 2022 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library.  This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, or (at your option)
> +// any later version.
> +
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +// GNU General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3.  If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +// { dg-options "-std=gnu++23" }
> +// { dg-do compile { target c++23 } }
> +
> +#include <version>
> +
> +#ifndef __cpp_lib_reference_from_temporary
> +# error "Feature test macro for reference_from_temporary is missing in
> <version>"
> +#elif __cpp_lib_reference_from_temporary < 202202L
> +# error "Feature test macro for reference_from_temporary has wrong value
> in <version>"
> +#endif
> diff --git
> a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
> b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
> index 9b3957f7d47..2b03ad7067d 100644
> --- a/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
> +++ b/libstdc++-v3/testsuite/20_util/variable_templates_for_traits.cc
> @@ -346,3 +346,17 @@ static_assert(disjunction_v<false_type, false_type,
>                true_type>, "");
>  static_assert(!disjunction_v<false_type, false_type,
>                false_type>, "");
> +#if __cpp_lib_reference_from_temporary >= 202202L
> +static_assert(std::reference_converts_from_temporary_v<int&&, int>
> +             && std::reference_converts_from_temporary_v<const int&, int>
> +             && !std::reference_converts_from_temporary_v<int&&, int&&>
> +             && !std::reference_converts_from_temporary_v<const int&,
> int&&>
> +             && std::reference_converts_from_temporary_v<int&&, long&&>
> +             && std::reference_converts_from_temporary_v<int&&, long>,
> "");
> +static_assert(std::reference_constructs_from_temporary_v<int&&, int>
> +             && std::reference_constructs_from_temporary_v<const int&,
> int>
> +             && !std::reference_constructs_from_temporary_v<int&&, int&&>
> +             && !std::reference_constructs_from_temporary_v<const int&,
> int&&>
> +             && std::reference_constructs_from_temporary_v<int&&, long&&>
> +             && std::reference_constructs_from_temporary_v<int&&, long>,
> "");
> +#endif
>
> base-commit: 23dd41c480fa9f06c33c1e6090bbae53869f85af
> --
> 2.36.1
>
>

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-15 19:59           ` [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Ville Voutilainen
@ 2022-07-15 20:25             ` Marek Polacek
  2022-07-17 21:12               ` Stephan Bergmann
  0 siblings, 1 reply; 15+ messages in thread
From: Marek Polacek @ 2022-07-15 20:25 UTC (permalink / raw)
  To: Ville Voutilainen; +Cc: Jason Merrill, Jonathan Wakely, libstdc++, GCC Patches

On Fri, Jul 15, 2022 at 10:59:41PM +0300, Ville Voutilainen wrote:
> Well, is_xible is not is_xible_p because it doesn't need to be both is_*
> and *_p. But xes_from_temporary is less obviously a question, so
> xes_from_temporary_p would imho be a better name.

Yeah, I guess so.  But I've already pushed the patch.

Marek


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
  2022-07-15 20:25             ` [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
@ 2022-07-17 21:12               ` Stephan Bergmann
  2022-07-19 10:08                 ` [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Jonathan Wakely
  0 siblings, 1 reply; 15+ messages in thread
From: Stephan Bergmann @ 2022-07-17 21:12 UTC (permalink / raw)
  To: Marek Polacek; +Cc: libstdc++, GCC Patches

On 7/15/22 22:25, Marek Polacek via Gcc-patches wrote:
> Yeah, I guess so.  But I've already pushed the patch.

This commit obviously breaks using libstdc++ with Clang (in -std=c++2b 
mode), which doesn't implement those new builtins.  Something like the 
below would fix that,

> diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
> index b1a1deecf66..a6e028b42ec 100644
> --- a/libstdc++-v3/include/std/type_traits
> +++ b/libstdc++-v3/include/std/type_traits
> @@ -3505,6 +3505,7 @@ template<typename _Ret, typename _Fn, typename... _Args>
>    template<typename _Tp>
>      inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
>  
> +#if __has_builtin(__reference_constructs_from_temporary) && __has_builtin(__reference_converts_from_temporary)
>  #define __cpp_lib_reference_from_temporary 202202L
>  
>    /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
> @@ -3544,6 +3545,7 @@ template<typename _Ret, typename _Fn, typename... _Args>
>    template<typename _Tp, typename _Up>
>      inline constexpr bool reference_converts_from_temporary_v
>        = reference_converts_from_temporary<_Tp, _Up>::value;
> +#endif
>  #endif // C++23
>  
>  #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
> diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
> index 5edca2f3007..7c4b7f7cc6d 100644
> --- a/libstdc++-v3/include/std/version
> +++ b/libstdc++-v3/include/std/version
> @@ -304,7 +304,9 @@
>  #define __cpp_lib_byteswap 202110L
>  #define __cpp_lib_constexpr_typeinfo 202106L
>  #define __cpp_lib_is_scoped_enum 202011L
> +#if __has_builtin(__reference_constructs_from_temporary) && __has_builtin(__reference_converts_from_temporary)
>  #define __cpp_lib_reference_from_temporary 202202L
> +#endif
>  
>  #if _GLIBCXX_HOSTED
>  #define __cpp_lib_adaptor_iterator_pair_constructor 202106L


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477]
  2022-07-17 21:12               ` Stephan Bergmann
@ 2022-07-19 10:08                 ` Jonathan Wakely
  2022-07-19 11:10                   ` Jonathan Wakely
  0 siblings, 1 reply; 15+ messages in thread
From: Jonathan Wakely @ 2022-07-19 10:08 UTC (permalink / raw)
  To: Stephan Bergmann; +Cc: Marek Polacek, libstdc++, GCC Patches

On Sun, 17 Jul 2022 at 22:13, Stephan Bergmann via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> On 7/15/22 22:25, Marek Polacek via Gcc-patches wrote:
> > Yeah, I guess so.  But I've already pushed the patch.
>
> This commit obviously breaks using libstdc++ with Clang (in -std=c++2b
> mode), which doesn't implement those new builtins.  Something like the
> below would fix that,

Thanks, Stephan, I'll fix this.


>
> > diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
> > index b1a1deecf66..a6e028b42ec 100644
> > --- a/libstdc++-v3/include/std/type_traits
> > +++ b/libstdc++-v3/include/std/type_traits
> > @@ -3505,6 +3505,7 @@ template<typename _Ret, typename _Fn, typename... _Args>
> >    template<typename _Tp>
> >      inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
> >
> > +#if __has_builtin(__reference_constructs_from_temporary) && __has_builtin(__reference_converts_from_temporary)
> >  #define __cpp_lib_reference_from_temporary 202202L
> >
> >    /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
> > @@ -3544,6 +3545,7 @@ template<typename _Ret, typename _Fn, typename... _Args>
> >    template<typename _Tp, typename _Up>
> >      inline constexpr bool reference_converts_from_temporary_v
> >        = reference_converts_from_temporary<_Tp, _Up>::value;
> > +#endif
> >  #endif // C++23
> >
> >  #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
> > diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
> > index 5edca2f3007..7c4b7f7cc6d 100644
> > --- a/libstdc++-v3/include/std/version
> > +++ b/libstdc++-v3/include/std/version
> > @@ -304,7 +304,9 @@
> >  #define __cpp_lib_byteswap 202110L
> >  #define __cpp_lib_constexpr_typeinfo 202106L
> >  #define __cpp_lib_is_scoped_enum 202011L
> > +#if __has_builtin(__reference_constructs_from_temporary) && __has_builtin(__reference_converts_from_temporary)
> >  #define __cpp_lib_reference_from_temporary 202202L
> > +#endif
> >
> >  #if _GLIBCXX_HOSTED
> >  #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
>


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477]
  2022-07-19 10:08                 ` [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Jonathan Wakely
@ 2022-07-19 11:10                   ` Jonathan Wakely
  2022-07-19 14:14                     ` Jonathan Wakely
  0 siblings, 1 reply; 15+ messages in thread
From: Jonathan Wakely @ 2022-07-19 11:10 UTC (permalink / raw)
  To: Stephan Bergmann; +Cc: Marek Polacek, libstdc++, GCC Patches

On Tue, 19 Jul 2022 at 11:08, Jonathan Wakely wrote:
>
> On Sun, 17 Jul 2022 at 22:13, Stephan Bergmann via Libstdc++
> <libstdc++@gcc.gnu.org> wrote:
> >
> > On 7/15/22 22:25, Marek Polacek via Gcc-patches wrote:
> > > Yeah, I guess so.  But I've already pushed the patch.
> >
> > This commit obviously breaks using libstdc++ with Clang (in -std=c++2b
> > mode), which doesn't implement those new builtins.  Something like the
> > below would fix that,
>
> Thanks, Stephan, I'll fix this.

This patch doesn't work, because __has_builtin doesn't detect the new
built-ins. I have a patch that solves that, so we can make the change
to the library headers.

>
> >
> > > diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
> > > index b1a1deecf66..a6e028b42ec 100644
> > > --- a/libstdc++-v3/include/std/type_traits
> > > +++ b/libstdc++-v3/include/std/type_traits
> > > @@ -3505,6 +3505,7 @@ template<typename _Ret, typename _Fn, typename... _Args>
> > >    template<typename _Tp>
> > >      inline constexpr bool is_scoped_enum_v = is_scoped_enum<_Tp>::value;
> > >
> > > +#if __has_builtin(__reference_constructs_from_temporary) && __has_builtin(__reference_converts_from_temporary)
> > >  #define __cpp_lib_reference_from_temporary 202202L
> > >
> > >    /// True if _Tp is a reference type, a _Up value can be bound to _Tp in
> > > @@ -3544,6 +3545,7 @@ template<typename _Ret, typename _Fn, typename... _Args>
> > >    template<typename _Tp, typename _Up>
> > >      inline constexpr bool reference_converts_from_temporary_v
> > >        = reference_converts_from_temporary<_Tp, _Up>::value;
> > > +#endif
> > >  #endif // C++23
> > >
> > >  #if _GLIBCXX_HAVE_IS_CONSTANT_EVALUATED
> > > diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
> > > index 5edca2f3007..7c4b7f7cc6d 100644
> > > --- a/libstdc++-v3/include/std/version
> > > +++ b/libstdc++-v3/include/std/version
> > > @@ -304,7 +304,9 @@
> > >  #define __cpp_lib_byteswap 202110L
> > >  #define __cpp_lib_constexpr_typeinfo 202106L
> > >  #define __cpp_lib_is_scoped_enum 202011L
> > > +#if __has_builtin(__reference_constructs_from_temporary) && __has_builtin(__reference_converts_from_temporary)
> > >  #define __cpp_lib_reference_from_temporary 202202L
> > > +#endif
> > >
> > >  #if _GLIBCXX_HOSTED
> > >  #define __cpp_lib_adaptor_iterator_pair_constructor 202106L
> >


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477]
  2022-07-19 11:10                   ` Jonathan Wakely
@ 2022-07-19 14:14                     ` Jonathan Wakely
  0 siblings, 0 replies; 15+ messages in thread
From: Jonathan Wakely @ 2022-07-19 14:14 UTC (permalink / raw)
  To: Stephan Bergmann; +Cc: Marek Polacek, libstdc++, GCC Patches

On Tue, 19 Jul 2022 at 12:10, Jonathan Wakely wrote:
>
> On Tue, 19 Jul 2022 at 11:08, Jonathan Wakely wrote:
> >
> > On Sun, 17 Jul 2022 at 22:13, Stephan Bergmann via Libstdc++
> > <libstdc++@gcc.gnu.org> wrote:
> > >
> > > On 7/15/22 22:25, Marek Polacek via Gcc-patches wrote:
> > > > Yeah, I guess so.  But I've already pushed the patch.
> > >
> > > This commit obviously breaks using libstdc++ with Clang (in -std=c++2b
> > > mode), which doesn't implement those new builtins.  Something like the
> > > below would fix that,
> >
> > Thanks, Stephan, I'll fix this.
>
> This patch doesn't work, because __has_builtin doesn't detect the new
> built-ins. I have a patch that solves that, so we can make the change
> to the library headers.

Should be fixed now, thanks for reporting it.


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2022-07-19 14:14 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-08 17:41 [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Marek Polacek
2022-07-11 10:19 ` Jonathan Wakely
2022-07-12 17:16   ` [PATCH v2] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
2022-07-12 20:10 ` [PATCH] " Jason Merrill
2022-07-12 20:15   ` Jason Merrill
2022-07-14 17:43     ` Marek Polacek
2022-07-15  3:48       ` Jason Merrill
2022-07-15 15:32         ` Marek Polacek
2022-07-15 19:59           ` [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Ville Voutilainen
2022-07-15 20:25             ` [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
2022-07-17 21:12               ` Stephan Bergmann
2022-07-19 10:08                 ` [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Jonathan Wakely
2022-07-19 11:10                   ` Jonathan Wakely
2022-07-19 14:14                     ` Jonathan Wakely
2022-07-14 17:41   ` [PATCH v3] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek

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