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
