[gcc r17-2665] c++: implement LWG 3819, reference_xes_from_temporary [PR112908]

Marek Polacek via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
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));
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.