From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 223C3385F178 for ; Fri, 21 Oct 2022 23:29:08 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 223C3385F178 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1666394920; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=HpDrRcSWvN87C1C4uopf2q/nf+KIIfCIJhfLG4SKj1Y=; b=GYCcTWcsZBWUU8DLQGp/zJBP+RpflOtzfaWqvxlTqKytGZKRcCIflqbt1R0UI/jkGthAPg jyWfaJmvb/2BB/FIWHMSRapgMLXGzD6eA6/QXDSrLC8pPzGVb1WnFTuLuUM8J+lGeia3Ky eS3QYAtbC31pbm2UiBdVrlRssZQJ3gw= Received: from mimecast-mx02.redhat.com (mx3-rdu2.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-604-G1NFVnI_NoaGmsofOXejVg-1; Fri, 21 Oct 2022 19:28:39 -0400 X-MC-Unique: G1NFVnI_NoaGmsofOXejVg-1 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.rdu2.redhat.com [10.11.54.1]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id A3BA53C0F371 for ; Fri, 21 Oct 2022 23:28:37 +0000 (UTC) Received: from pdp-11.lan (unknown [10.22.9.195]) by smtp.corp.redhat.com (Postfix) with ESMTP id 636F240C2064; Fri, 21 Oct 2022 23:28:35 +0000 (UTC) From: Marek Polacek To: Jason Merrill , GCC Patches , Jonathan Wakely Subject: [PATCH] c++: Implement -Wdangling-reference [PR106393] Date: Fri, 21 Oct 2022 19:28:24 -0400 Message-Id: <20221021232824.1093138-1-polacek@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.1 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-12.5 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_NONE,TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: This patch implements a new experimental warning (enabled by -Wextra) to detect references bound to temporaries whose lifetime has ended. The primary motivation is the Note in : Capturing the result of std::max by reference produces a dangling reference if one of the parameters is a temporary and that parameter is returned: int n = 1; const int& r = std::max(n-1, n+1); // r is dangling That's because both temporaries for n-1 and n+1 are destroyed at the end of the full expression. With this warning enabled, you'll get: g.C:3:12: warning: possibly dangling reference to a temporary [-Wdangling-reference] 3 | const int& r = std::max(n-1, n+1); | ^ g.C:3:24: note: the temporary was destroyed at the end of the full expression 'std::max((n - 1), (n + 1))' 3 | const int& r = std::max(n-1, n+1); | ~~~~~~~~^~~~~~~~~~ The warning works by checking if a reference is initialized with a function that returns a reference, and at least one parameter of the function is a reference that is bound to a temporary. It assumes that such a function actually returns one of its arguments! (I added code to check_return_expr to suppress the warning when we've seen the definition of the function and we can say that it can return something other than its parameter.) It doesn't warn when the function in question is a member function, otherwise it'd emit loads of warnings for valid code like obj.emplace({0}, 0). It warns in member initializer lists as well: const int& f(const int& i) { return i; } struct S { const int &r; // { dg-warning "dangling reference" } S() : r(f(10)) { } // { dg-message "destroyed" } }; I've run the testsuite/bootstrap with the warning enabled by default. There were just a few FAILs: * g++.dg/warn/Wdangling-pointer-2.C * 20_util/any/misc/any_cast.cc * 20_util/forward/c_neg.cc * 20_util/forward/f_neg.cc * experimental/any/misc/any_cast.cc all of these look like genuine bugs. A bootstrap with the warning enabled by default passed. When testing a previous version of the patch, there were many FAILs in libstdc++'s 22_locale/; all of them because the warning triggered on const test_type& obj = std::use_facet(std::locale()); but this code looks valid -- std::use_facet doesn't return a reference to its parameter. Therefore I added code to suppress the warning when the call is std::use_facet. Now 22_locale/* pass even with the warning on. We could exclude more std:: functions like this if desirable. Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? PR c++/106393 gcc/c-family/ChangeLog: * c.opt (Wdangling-reference): New. gcc/cp/ChangeLog: * call.cc (expr_represents_temporary_p): New, factored out of conv_binds_ref_to_temporary. (conv_binds_ref_to_temporary): Don't return false just because a ck_base is missing. Use expr_represents_temporary_p. (find_initializing_call_expr): New. (do_warn_dangling_reference): New. (extend_ref_init_temps): Call do_warn_dangling_reference. * typeck.cc (check_return_expr): Suppress -Wdangling-reference warnings. gcc/ChangeLog: * doc/invoke.texi: Document -Wdangling-reference. gcc/testsuite/ChangeLog: * g++.dg/cpp23/elision4.C: Use -Wdangling-reference, add dg-warning. * g++.dg/cpp23/elision7.C: Likewise. * g++.dg/warn/Wdangling-reference1.C: New test. * g++.dg/warn/Wdangling-reference2.C: New test. --- gcc/c-family/c.opt | 4 + gcc/cp/call.cc | 138 ++++++++++++++++-- gcc/cp/cp-tree.h | 4 +- gcc/cp/typeck.cc | 10 ++ gcc/doc/invoke.texi | 34 ++++- gcc/testsuite/g++.dg/cpp23/elision4.C | 5 +- gcc/testsuite/g++.dg/cpp23/elision7.C | 3 +- .../g++.dg/warn/Wdangling-reference1.C | 103 +++++++++++++ .../g++.dg/warn/Wdangling-reference2.C | 28 ++++ 9 files changed, 312 insertions(+), 17 deletions(-) create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference1.C create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference2.C diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 01d480759ae..02d79991aeb 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -555,6 +555,10 @@ Wdangling-pointer= C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_dangling_pointer) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) IntegerRange(0, 2) Warn for uses of pointers to auto variables whose lifetime has ended. +Wdangling-reference +C++ ObjC++ Var(warn_dangling_reference) Warning LangEnabledBy(C++ ObjC++, Wextra) +Warn when a reference is bound to a temporary whose lifetime has ended. + Wdate-time C ObjC C++ ObjC++ CPP(warn_date_time) CppReason(CPP_W_DATE_TIME) Var(cpp_warn_date_time) Init(0) Warning Warn about __TIME__, __DATE__ and __TIMESTAMP__ usage. diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 6a34e9c2ae1..43e607987a0 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -9313,6 +9313,16 @@ conv_binds_ref_to_prvalue (conversion *c) return conv_is_prvalue (next_conversion (c)); } +/* True iff EXPR represents a (subobject of a) temporary. */ + +static bool +expr_represents_temporary_p (tree expr) +{ + while (handled_component_p (expr)) + expr = TREE_OPERAND (expr, 0); + return TREE_CODE (expr) == TARGET_EXPR; +} + /* True iff C is a conversion that binds a reference to a temporary. This is a superset of conv_binds_ref_to_prvalue: here we're also interested in xvalues. */ @@ -9330,18 +9340,14 @@ conv_binds_ref_to_temporary (conversion *c) struct Derived : Base {}; const Base& b(Derived{}); where we bind 'b' to the Base subobject of a temporary object of type - Derived. The subobject is an xvalue; the whole object is a prvalue. */ - if (c->kind != ck_base) - return false; - c = next_conversion (c); - if (c->kind == ck_identity && c->u.expr) - { - tree expr = c->u.expr; - while (handled_component_p (expr)) - expr = TREE_OPERAND (expr, 0); - if (TREE_CODE (expr) == TARGET_EXPR) - return true; - } + Derived. The subobject is an xvalue; the whole object is a prvalue. + + The ck_base doesn't have to be present for cases like X{}.m. */ + if (c->kind == ck_base) + c = next_conversion (c); + if (c->kind == ck_identity && c->u.expr + && expr_represents_temporary_p (c->u.expr)) + return true; return false; } @@ -13428,6 +13434,111 @@ initialize_reference (tree type, tree expr, return expr; } +/* Helper for do_warn_dangling_reference to find a non-nested CALL_EXPR + that initializes the LHS, or NULL_TREE if none found. For instance: + + const int& r = (42, f(1)); // f(1) + const int& t = b ? f(1) : f(2); // f(1) + const int& z = (f(1), 42); // NULL_TREE + + EXPR is the initializer. */ + +static tree +find_initializing_call_expr (tree expr) +{ + STRIP_NOPS (expr); + switch (TREE_CODE (expr)) + { + case CALL_EXPR: + return expr; + case COMPOUND_EXPR: + return find_initializing_call_expr (TREE_OPERAND (expr, 1)); + case COND_EXPR: + if (tree t = find_initializing_call_expr (TREE_OPERAND (expr, 1))) + return t; + return find_initializing_call_expr (TREE_OPERAND (expr, 2)); + case PAREN_EXPR: + return find_initializing_call_expr (TREE_OPERAND (expr, 0)); + default: + return NULL_TREE; + } +} + +/* Implement -Wdangling-reference, to detect cases like + + int n = 1; + const int& r = std::max(n - 1, n + 1); // r is dangling + + This creates temporaries from the arguments, returns a reference to + one of the temporaries, but both temporaries are destroyed at the end + of the full expression. + + This works by checking if a reference is initialized with a function + that returns a reference, and at least one parameter of the function + is a reference that is bound to a temporary. It assumes that such a + function actually returns one of its arguments. + + This warning doesn't warn when the function in question is a member + function. + + DECL is the reference being initialized, CALL is the initializer. */ + +static void +do_warn_dangling_reference (const_tree decl, tree call) +{ + if (!warn_dangling_reference) + return; + if (!TYPE_REF_P (TREE_TYPE (decl))) + return; + call = find_initializing_call_expr (call); + if (call == NULL_TREE) + return; + + tree fndecl = cp_get_callee_fndecl_nofold (call); + if (!fndecl + || warning_suppressed_p (fndecl, OPT_Wdangling_reference) + /* Don't warn about member functions; the warning would trigger in + valid code like + std::any a(...); + S& s = a.emplace({0}, 0); + which constructs a new object and returns a reference to it. */ + || DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl) + /* It seems unreasonable to warn about operator functions. */ + || DECL_OVERLOADED_OPERATOR_P (fndecl) + /* If the function doesn't return a reference, don't warn. This can + be e.g. + const int& z = std::min({1, 2, 3, 4, 5, 6, 7}); + which doesn't dangle: std::min here returns an int. */ + || !TYPE_REF_P (TREE_TYPE (TREE_TYPE (fndecl)))) + return; + + /* Mitigate some known cases. */ + if (decl_in_std_namespace_p (fndecl)) + if (tree name = DECL_NAME (fndecl)) + if (id_equal (name, "use_facet")) + return; + + for (int i = 0; i < call_expr_nargs (call); ++i) + { + /* We're looking to see if ARG is something like + (const int &) &TARGET_EXPR <...>. */ + tree arg = CALL_EXPR_ARG (call, i); + STRIP_NOPS (arg); + if (TREE_CODE (arg) == ADDR_EXPR) + arg = TREE_OPERAND (arg, 0); + if (expr_represents_temporary_p (arg)) + { + auto_diagnostic_group d; + if (warning_at (DECL_SOURCE_LOCATION (decl), + OPT_Wdangling_reference, + "possibly dangling reference to a temporary")) + inform (EXPR_LOCATION (call), "the temporary was destroyed at " + "the end of the full expression %qE", call); + return; + } + } +} + /* If *P is an xvalue expression, prevent temporary lifetime extension if it gets used to initialize a reference. */ @@ -13525,6 +13636,9 @@ extend_ref_init_temps (tree decl, tree init, vec **cleanups, tree type = TREE_TYPE (init); if (processing_template_decl) return init; + + do_warn_dangling_reference (decl, init); + if (TYPE_REF_P (type)) init = extend_ref_init_temps_1 (decl, init, cleanups, cond_guard); else diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 60a25101049..8ddf55ce2b0 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -459,7 +459,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; TI_PENDING_TEMPLATE_FLAG. TEMPLATE_PARMS_FOR_INLINE. DELETE_EXPR_USE_VEC (in DELETE_EXPR). - (TREE_CALLS_NEW) (in _EXPR or _REF) (commented-out). ICS_ELLIPSIS_FLAG (in _CONV) DECL_INITIALIZED_P (in VAR_DECL) TYPENAME_IS_CLASS_P (in TYPENAME_TYPE) @@ -4558,6 +4557,9 @@ get_vec_init_expr (tree t) When appearing in a CONSTRUCTOR, the expression is an unconverted compound literal. + When appearing in a CALL_EXPR, it means that it is a call to + a constructor. + When appearing in a FIELD_DECL, it means that this field has been duly initialized in its constructor. */ #define TREE_HAS_CONSTRUCTOR(NODE) (TREE_LANG_FLAG_4 (NODE)) diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc index 16e7d85793d..5a22eee8ebf 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -11238,6 +11238,16 @@ check_return_expr (tree retval, bool *no_warning) if (processing_template_decl) return saved_retval; + /* A naive attempt to reduce the number of -Wdangling-reference false + positives: if we know that this function can return something other + than one of its parameters, suppress the warning. */ + if (warn_dangling_reference + && TYPE_REF_P (functype) + && bare_retval + && (!REFERENCE_REF_P (bare_retval) + || TREE_CODE (TREE_OPERAND (bare_retval, 0)) != PARM_DECL)) + suppress_warning (current_function_decl, OPT_Wdangling_reference); + /* Actually copy the value returned into the appropriate location. */ if (retval && retval != result) retval = cp_build_init_expr (result, retval); diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 09548c4528c..28bb395ce6c 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -249,7 +249,8 @@ in the following sections. -Wno-class-conversion -Wclass-memaccess @gol -Wcomma-subscript -Wconditionally-supported @gol -Wno-conversion-null -Wctad-maybe-unsupported @gol --Wctor-dtor-privacy -Wno-delete-incomplete @gol +-Wctor-dtor-privacy -Wdangling-reference @gol +-Wno-delete-incomplete @gol -Wdelete-non-virtual-dtor -Wno-deprecated-array-compare @gol -Wdeprecated-copy -Wdeprecated-copy-dtor @gol -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol @@ -3627,6 +3628,36 @@ public static member functions. Also warn if there are no non-private methods, and there's at least one private member function that isn't a constructor or destructor. +@item -Wdangling-reference @r{(C++ and Objective-C++ only)} +@opindex Wdangling-reference +@opindex Wno-dangling-reference +Warn when a reference is bound to a temporary whose lifetime has ended. +For example: + +@smallexample +int n = 1; +const int& r = std::max(n - 1, n + 1); // r is dangling +@end smallexample + +In the example above, two temporaries are created, one for each +argument, and a reference to one of the temporaries is returned. +However, both temporaries are destroyed at the end of the full +expression, so the reference @code{r} is dangling. This warning +also detects dangling references in member initializer lists: + +@smallexample +const int& f(const int& i) @{ return i; @} +struct S @{ + const int &r; // r is dangling + S() : r(f(10)) @{ @} +@}; +@end smallexample + +Member functions are not checked. Certain standard functions, such +as @code{std::use_facet}, are also excluded from checking. + +This warning is enabled by @option{-Wextra}. + @item -Wdelete-non-virtual-dtor @r{(C++ and Objective-C++ only)} @opindex Wdelete-non-virtual-dtor @opindex Wno-delete-non-virtual-dtor @@ -5936,6 +5967,7 @@ name is still supported, but the newer name is more descriptive.) @gccoptlist{-Wclobbered @gol -Wcast-function-type @gol +-Wdangling-reference @r{(C++ only)} @gol -Wdeprecated-copy @r{(C++ only)} @gol -Wempty-body @gol -Wenum-conversion @r{(C only)} @gol diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C index c19b86b8b5f..d39053ad741 100644 --- a/gcc/testsuite/g++.dg/cpp23/elision4.C +++ b/gcc/testsuite/g++.dg/cpp23/elision4.C @@ -1,5 +1,6 @@ // PR c++/101165 - P2266R1 - Simpler implicit move // { dg-do compile { target c++23 } } +// { dg-options "-Wdangling-reference" } // Test from P2266R1, $ 5.2. LibreOffice OString constructor. struct X { @@ -33,6 +34,6 @@ T& temporary2(T&& x) { return static_cast(x); } void test () { - int& r1 = temporary1 (42); - int& r2 = temporary2 (42); + int& r1 = temporary1 (42); // { dg-warning "dangling reference" } + int& r2 = temporary2 (42); // { dg-warning "dangling reference" } } diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C index 19fa89ae133..0045842b34f 100644 --- a/gcc/testsuite/g++.dg/cpp23/elision7.C +++ b/gcc/testsuite/g++.dg/cpp23/elision7.C @@ -1,5 +1,6 @@ // PR c++/101165 - P2266R1 - Simpler implicit move // { dg-do compile { target c++23 } } +// { dg-options "-Wdangling-reference" } struct X { X (); @@ -68,5 +69,5 @@ f7 (T &&t) void do_f7 () { - const int &x = f7 (0); + const int &x = f7 (0); // { dg-warning "dangling reference" } } diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C new file mode 100644 index 00000000000..e0324d38f67 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C @@ -0,0 +1,103 @@ +// PR c++/106393 +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +const int& f(const int& i) { return i; } +const int& h(int); +int g; +const int& globref(const int&) { return g; } +struct X { + int* i; + operator const int&() const { return *i; } +}; +X x{&g}; + +const int& r1 = f(10); // { dg-warning "dangling reference" } +// r2 = _ZGR2r2_ = (int) *f ((const int &) &TARGET_EXPR ) + 1; (const int &) &_ZGR2r2_ +const int& r2 = f(10) + 1; +// Don't warn here, we have +// r3 = f (X::operator const int& (&x)) +const int& r3 = f(x); +// Don't warn here, because we've seen the definition of globref +// and could figure out that it may not return one of its parms. +// Questionable -- it can also hide bugs --, but it helps here. +const int& r4 = globref(1); +const int& r5 = (42, f(10)); // { dg-warning "dangling reference" } +const int& r6 = (f(10), 42); +const int& r7 = (f(10)); // { dg-warning "dangling reference" } +const int& r8 = g ? f(10) : f(9); // { dg-warning "dangling reference" } +const int& r9 = (42, g ? f(10) : f(9)); // { dg-warning "dangling reference" } +const int& r10 = (g ? f(10) : f(9), 42); +// Binds to a reference temporary for r11. +const int& r11 = g ? f(10) : 9; +// Invalid, but we don't warn here yet. +// r12 = f (f ((const int &) &TARGET_EXPR )) +const int& r12 = f(f(1)); +const int& r13 = h(f(1)); +// Other forms of initializers. +const int& r14(f(10)); // { dg-warning "dangling reference" } +const int& r15(f(10)); // { dg-warning "dangling reference" } +// Returns a ref, but doesn't have a parameter of reference type. +const int& r16 = h(10); + +// OK: the reference is bound to the 10 so still valid at the point +// where it's copied into i1. +int i1 = f(10); + +int +test1 () +{ + const int &lr = f(10); // { dg-warning "dangling reference" } + int i2 = f(10); + return lr; +} + +struct B { }; +struct D : B { }; +struct C { + D d; +}; + +C c; +D d; + +using U = D[3]; + +const B& frotz(const D&); +const B& b1 = frotz(C{}.d); // { dg-warning "dangling reference" } +const B& b2 = frotz(D{}); // { dg-warning "dangling reference" } +const B& b3 = frotz(c.d); +const B& b4 = frotz(d); +const B& b5 = frotz(U{}[0]); // { dg-warning "dangling reference" } + +struct E { + E(int); +}; +const E& operator*(const E&); +const E& b6 = *E(1); + +struct S { + const int &r; // { dg-warning "dangling reference" } + S() : r(f(10)) { } // { dg-message "destroyed" } +}; + +// From cppreference. +template +const T& max(const T& a, const T& b) +{ + return (a < b) ? b : a; +} + +int n = 1; +const int& r20 = max(n - 1, n + 1); // { dg-warning "dangling reference" } + +// Don't warn about member functions. +struct Y { + operator int&&(); + const int& foo(const int&); +}; + +// x1 = Y::operator int&& (&TARGET_EXPR ) +int&& x1 = Y(); +int&& x2 = Y{}; +const int& t1 = Y().foo(10); diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C new file mode 100644 index 00000000000..dafdb43f1b9 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C @@ -0,0 +1,28 @@ +// PR c++/106393 +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +namespace std { +struct any {}; +template _ValueType any_cast(any &&); +template struct remove_reference { using type = _Tp; }; +template _Tp forward(typename remove_reference<_Tp>::type); +template typename remove_reference<_Tp>::type move(_Tp); +} // namespace std + +const int &r = std::any_cast(std::any()); // { dg-warning "dangling reference" } + +template struct C { + T t_; // { dg-warning "dangling reference" } + C(T); + template C(U c) : t_(std::forward(c.t_)) {} +}; +struct A {}; +struct B { + B(A); +}; +int main() { + A a; + C ca(a); + C(std::move(ca)); +} base-commit: 5792208f5124f687376f25798668d105d7ddb270 -- 2.37.3