https://gcc.gnu.org/g:83a903f339af9a1ecde675261191cc69e7b1b721

commit r17-2665-g83a903f339af9a1ecde675261191cc69e7b1b721
Author: Marek Polacek <[email protected]>
Date:   Tue Jul 21 12:59:40 2026 -0400

    c++: implement LWG 3819, reference_xes_from_temporary [PR112908]
    
    This is an attempt to implement <https://cplusplus.github.io/LWG/issue3819>.
    
    My understanding of this issue is that previously, ref_xes_from_temporary
    was defined by using is_constructible, which is implemented by seeing
    if
      T t(declval<Args>()...);
    is well-formed.  But declval always yields an xvalue, never a prvalue.
    In practice this means that for
    
        struct U {
          U();
          U(U&&) = delete;
        };
    
        struct T {
          T(U);
        };
    
    reference_constructs_from_temporary_v<const T&, U> is false due
    to the deleted move ctor.  But if we have a prvalue, then the
    call to the move ctor should be elided and so it doesn't matter
    that it's deleted.  So the result should be 'true'.
    
    Our ref_xes_from_temporary already doesn't check is_constructible<T, U>
    as the comment says, but we always use build_trait_object which
    gives us an xvalue.  What we need is to implement [meta.unary.prop]/5.2:
    Otherwise [not a reference or function type], VAL<T> is a prvalue that
    initially has type T.  For this I've added build_prvalue_trait_object.
    
    The finish_trait_expr change is so that get_target_expr doesn't crash
    on an incomplete type.  This change should be correct since
    https://cplusplus.github.io/LWG/issue2939 didn't adjust
    reference_xes_from_temporary the same way as is_convertible/constructible.
    
            PR c++/112908
    
    gcc/cp/ChangeLog:
    
            * method.cc (build_prvalue_trait_object): New.
            (ref_xes_from_temporary): Use build_prvalue_trait_object.
            Use deferring_access_check_sentinel and cp_unevaluated.  Don't
            call force_rvalue or rvalue.
            * semantics.cc (finish_trait_expr)
            <case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY>: Actually check
            completeness.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/ext/is_constructible8.C: Move
            __reference_*_from_temporary testing to a new test.
            * g++.dg/ext/reference_xes_from_temporary2.C: New test.
            * g++.dg/ext/reference_xes_from_temporary3.C: New test.
    
    Reviewed-by: Jason Merrill <[email protected]>

Diff:
---
 gcc/cp/method.cc                                   | 37 ++++++++--
 gcc/cp/semantics.cc                                |  4 +-
 gcc/testsuite/g++.dg/ext/is_constructible8.C       |  8 --
 .../g++.dg/ext/reference_xes_from_temporary2.C     | 17 +++++
 .../g++.dg/ext/reference_xes_from_temporary3.C     | 86 ++++++++++++++++++++++
 5 files changed, 134 insertions(+), 18 deletions(-)

diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index cec98bdc89b6..bf882b9619c0 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -1957,6 +1957,19 @@ build_trait_object (tree type, tsubst_flags_t complain)
   return build_stub_object (type);
 }
 
+/* Build up an object for [meta.unary.prop]/5.2:
+   Otherwise [not a reference or function type], VAL<T> is a prvalue that
+   initially has type T.  */
+
+static tree
+build_prvalue_trait_object (tree t)
+{
+  if (CLASS_TYPE_P (t))
+    return force_target_expr (t, void_node, tf_none);
+  else
+    return build1 (CONVERT_EXPR, t, integer_one_node);
+}
+
 /* [func.require] Build an expression of INVOKE(FN_TYPE, ARG_TYPES...).  If the
    given is not invocable, returns error_mark_node, unless COMPLAIN includes
    tf_error.  */
@@ -2516,12 +2529,17 @@ is_xible (enum tree_code code, tree to, tree from, bool 
explain/*=false*/)
   return !!expr;
 }
 
-/* Return true iff conjunction_v<is_reference<T>, is_constructible<T, U>> is
-   true, and the initialization
+/* Return true iff T is a reference type, 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.
+   is well-formed and binds t to a temporary object whose lifetime is
+   extended.
+   The full-expression of the variable initialization is treated as an
+   unevaluated operand.  Access checking is performed as if in a context
+   unrelated to T and U.  Only the validity of the immediate context of
+   the variable initialization is considered.
+
    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>().
@@ -2533,13 +2551,16 @@ 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_trait_object (from, tf_none);
+  deferring_access_check_sentinel acs (dk_no_deferred);
+  cp_unevaluated u;
+
+  tree val;
+  if (TYPE_REF_P (from) || TREE_CODE (from) == FUNCTION_TYPE)
+    val = build_trait_object (from, tf_none);
+  else
+    val = build_prvalue_trait_object (from);
   if (val == error_mark_node)
     return false;
-  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 (to, val, direct_init_p).is_true ();
 }
 
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 9ee28b7ace08..2274c6ab9b5b 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -14531,8 +14531,6 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, 
tree type1, tree type2,
     case CPTK_IS_NOTHROW_CONVERTIBLE:
     case CPTK_IS_NOTHROW_INVOCABLE:
     case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
-    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
-    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
       /* Don't check completeness for direct reference binding.  */;
       if (same_type_ref_bind_p (kind, type1, type2))
        break;
@@ -14541,6 +14539,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, 
tree type1, tree type2,
     case CPTK_IS_ASSIGNABLE:
     case CPTK_IS_NOTHROW_ASSIGNABLE:
     case CPTK_IS_TRIVIALLY_ASSIGNABLE:
+    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
+    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
       if (!check_trait_type (type1, /*kind=*/1, complain)
          || !check_trait_type (type2, /*kind=*/1, complain))
        return error_mark_node;
diff --git a/gcc/testsuite/g++.dg/ext/is_constructible8.C 
b/gcc/testsuite/g++.dg/ext/is_constructible8.C
index a27ec6eddd7c..073f7d5cbaab 100644
--- a/gcc/testsuite/g++.dg/ext/is_constructible8.C
+++ b/gcc/testsuite/g++.dg/ext/is_constructible8.C
@@ -21,11 +21,3 @@ SA (!__is_convertible(T, T&));
 SA (__is_nothrow_convertible(T, T&&));
 SA (__is_nothrow_convertible(T, const T&));
 SA (!__is_nothrow_convertible(T, T&));
-
-// All false because either the conversion fails or it doesn't bind a temporary
-SA (!__reference_constructs_from_temporary (T&&, T));
-SA (!__reference_constructs_from_temporary (const T&, T));
-SA (!__reference_constructs_from_temporary (T&, T));
-SA (!__reference_converts_from_temporary (T&&, T));
-SA (!__reference_converts_from_temporary (const T&, T));
-SA (!__reference_converts_from_temporary (T&, T));
diff --git a/gcc/testsuite/g++.dg/ext/reference_xes_from_temporary2.C 
b/gcc/testsuite/g++.dg/ext/reference_xes_from_temporary2.C
new file mode 100644
index 000000000000..e12c183fb409
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_xes_from_temporary2.C
@@ -0,0 +1,17 @@
+// PR c++/112908
+// { dg-do compile { target c++11 } }
+
+struct T;
+
+#define SA(X) static_assert ((X), #X);
+
+SA (!__reference_constructs_from_temporary (T&&, T));      // { dg-error 
"invalid use of incomplete type" }
+SA (!__reference_constructs_from_temporary (const T&, T));  // { dg-error 
"invalid use of incomplete type" }
+SA (!__reference_constructs_from_temporary (T&, T));       // { dg-error 
"invalid use of incomplete type" }
+SA (!__reference_converts_from_temporary (T&&, T));        // { dg-error 
"invalid use of incomplete type" }
+SA (!__reference_converts_from_temporary (const T&, T));    // { dg-error 
"invalid use of incomplete type" }
+SA (!__reference_converts_from_temporary (T&, T));         // { dg-error 
"invalid use of incomplete type" }
+
+struct Self {
+  static constexpr bool b = __reference_constructs_from_temporary (Self&, 
Self); // { dg-error "invalid use of incomplete type" }
+};
diff --git a/gcc/testsuite/g++.dg/ext/reference_xes_from_temporary3.C 
b/gcc/testsuite/g++.dg/ext/reference_xes_from_temporary3.C
new file mode 100644
index 000000000000..28a8622297aa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/reference_xes_from_temporary3.C
@@ -0,0 +1,86 @@
+// PR c++/112908
+// { dg-do compile { target c++17 } }
+
+struct U {
+  U();
+  U(U&&) = delete;
+};
+
+struct T {
+  T(U);
+};
+
+struct T2 {
+  explicit T2(U);
+};
+static_assert(__reference_converts_from_temporary(const T&, U));
+static_assert(__reference_constructs_from_temporary(const T&, U));
+static_assert(!__reference_converts_from_temporary(const T2&, U));
+static_assert(!__reference_constructs_from_temporary(const T2&, U));
+
+struct NonMovable {
+  NonMovable() = default;
+  NonMovable(NonMovable&&) = delete;
+};
+
+static_assert(__reference_converts_from_temporary(int&&, int));
+static_assert(__reference_converts_from_temporary(NonMovable&&, NonMovable));
+static_assert(__reference_constructs_from_temporary(int&&, int));
+static_assert(__reference_constructs_from_temporary(NonMovable&&, NonMovable));
+
+struct Base {
+  Base();
+  Base(Base&&) = delete;
+};
+struct Derived : Base {
+  Derived();
+  Derived(Derived&&) = delete;
+};
+
+static_assert(__reference_converts_from_temporary(const Base&, Derived));
+static_assert(__reference_constructs_from_temporary(const Base&, Derived));
+
+static_assert(!__reference_converts_from_temporary (int&, void));
+static_assert(!__reference_converts_from_temporary (int(&)[], int[]));
+static_assert(!__reference_constructs_from_temporary (int&, void));
+static_assert(!__reference_constructs_from_temporary (int(&)[], int[]));
+
+struct A {
+  A();
+  A(A&&) = delete;
+  ~A();
+};
+struct B { B(A); };
+static_assert (__reference_converts_from_temporary (const B&, A));
+static_assert (__reference_constructs_from_temporary (const B&, A));
+static_assert (__reference_converts_from_temporary (const B&, const A));
+static_assert (__reference_constructs_from_temporary (const B&, const A));
+
+struct C {
+  operator int() const;
+};
+static_assert(!__reference_converts_from_temporary(int&, C));
+static_assert(__reference_converts_from_temporary(const int&, C));
+static_assert(!__reference_constructs_from_temporary(int&, C));
+static_assert(__reference_constructs_from_temporary(const int&, C));
+
+struct D {
+  D(int);
+  D(D&&) = delete;
+};
+struct E { E(D); };
+static_assert(__reference_converts_from_temporary(const E&, D));
+static_assert(__reference_constructs_from_temporary(const E&, D));
+
+
+struct F {
+  F();
+  F(F&&) = delete;
+  virtual ~F();
+};
+
+struct G {
+  G(F);
+};
+static_assert(__reference_converts_from_temporary(const G&, F));
+static_assert(__reference_constructs_from_temporary(const G&, F));

Reply via email to