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