[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));