https://gcc.gnu.org/g:efb9667e2fb81dc0dd4dab25a84756c040bc3506
commit r17-607-gefb9667e2fb81dc0dd4dab25a84756c040bc3506 Author: Marek Polacek <[email protected]> Date: Thu May 14 09:27:17 2026 -0400 c++: capture of reference to global in template [PR123536] Thanks to DR 696 (r253266), this works: int g; void fn () { int &c = g; auto l = [] { c++; }; l(); } because `c` in the lambda body is not an odr-use because we can evaluate it to a constant and so there's no capture. But when fn is a template, we reject the code and crash. This patch fixes both. Outside a template, the call to maybe_constant_value in mark_use evaluates `c` to `(int&) &g` but in a template, it remains `c`. Then we emit an error, and crash on the error_mark_node from process_outer_var_ref. One of the reasons is else if (TYPE_REF_P (TREE_TYPE (expression))) /* FIXME cp_finish_decl doesn't fold reference initializers. */ return true; in value_dependent_expression_p but even if that changed, we still wouldn't get the referent because decl_really_constant_value wouldn't give it to us; the DECL_INITIAL is not a TREE_CONSTANT yet. So I stopped trying to make this work in a template, and instead I'm deferring the error in process_outer_var_ref to instantiation when it's instantiation-dependent. The VAR_P check there is not to regress the diagnostic in pr57416.C. The mark_use hunk is to fix a crash on invalid (lambda-const14.C). PR c++/123536 gcc/cp/ChangeLog: * cp-tree.h (process_outer_var_ref): Remove a parameter's name. * expr.cc (mark_use): Return if mark_rvalue_use returns error_mark_node. * semantics.cc (process_outer_var_ref): Return decl when it is instantiation-dependent. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/lambda/lambda-const12.C: New test. * g++.dg/cpp0x/lambda/lambda-const13.C: New test. * g++.dg/cpp0x/lambda/lambda-const14.C: New test. * g++.dg/template/local11.C: New test. Reviewed-by: Jason Merrill <[email protected]> Reviewed-by: Patrick Palka <[email protected]> Diff: --- gcc/cp/cp-tree.h | 3 +- gcc/cp/expr.cc | 2 + gcc/cp/semantics.cc | 9 ++- gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C | 74 ++++++++++++++++++++++ gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C | 19 ++++++ gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C | 16 +++++ gcc/testsuite/g++.dg/template/local11.C | 18 ++++++ 7 files changed, 138 insertions(+), 3 deletions(-) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 8a168f509d40..6a8adbc18caf 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -8584,7 +8584,8 @@ extern tree finish_base_specifier (tree, tree, bool, tree); extern void finish_member_declaration (tree); extern bool outer_automatic_var_p (tree); extern bool parsing_lambda_declarator (); -extern tree process_outer_var_ref (tree, tsubst_flags_t, bool force_use = false); +extern tree process_outer_var_ref (tree, tsubst_flags_t, + bool = false); extern cp_expr finish_id_expression (tree, tree, tree, cp_id_kind *, bool, bool, bool *, diff --git a/gcc/cp/expr.cc b/gcc/cp/expr.cc index 4d017d530ef7..516cea084a6f 100644 --- a/gcc/cp/expr.cc +++ b/gcc/cp/expr.cc @@ -182,6 +182,8 @@ mark_use (tree expr, bool rvalue_p, bool read_p, } } tree r = mark_rvalue_use (ref, loc, reject_builtin); + if (r == error_mark_node) + return error_mark_node; if (r != ref) { if (!rvalue_p) diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 490441dba6c0..0d47b20fb3c2 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -4595,7 +4595,8 @@ set_contract_capture_flag (tree d, bool val) id-expression, and we do lambda capture. */ tree -process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use) +process_outer_var_ref (tree decl, tsubst_flags_t complain, + bool odr_use/*=false*/) { if (cp_unevaluated_operand) { @@ -4697,6 +4698,10 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use) constant without odr-use. So don't complain yet. */ else if (!odr_use && decl_constant_var_p (var)) return var; + /* Don't complain when DECL is dependent, because it can turn out to + be constant (and therefore needing no capture) when instantiating. */ + else if (VAR_P (var) && instantiation_dependent_expression_p (var)) + return var; else if (lambda_expr) { if (complain & tf_error) @@ -4715,7 +4720,7 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use) } return error_mark_node; } - else if (processing_contract_condition && (TREE_CODE (decl) == PARM_DECL)) + else if (processing_contract_condition && TREE_CODE (decl) == PARM_DECL) /* Use of a parameter in a contract condition is fine. */ return decl; else diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C new file mode 100644 index 000000000000..e87627b7e249 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C @@ -0,0 +1,74 @@ +// PR c++/123536 +// { dg-do compile { target c++11 } } + +int g; + +struct S { + static int static_data_member; +}; + +template<int = 1> +void +fn1 () +{ + int &c = g; + auto l = [] { c++; }; + l(); +} + +template<int N = 1> +void +fn2 () +{ + const int &c = N; + // TODO This will be valid once we implement P2686. + auto l = [] { int i = c; (void) i; }; // { dg-error ".c. is not captured" } + l(); +} + +void +fn3 () +{ + int &c = g; + auto l = [] { c++; }; + l(); +} + +void +fn4 () +{ + int n = 42; + const int &c = n; + auto l = [] { int i = c; (void) i; }; // { dg-error ".c. is not captured" } + l(); +} + +template<class T> +void +fn5 () +{ + int& c = T::static_data_member; + auto l = [] { c++; }; + l(); +} + +template<class T> +void +fn6 () +{ + int& c = T::static_data_member; + [&c] { + [] { c++; }(); + }; +} + +void +bar () +{ + fn1 (); + fn2 (); + fn3 (); + fn4 (); + fn5<S>(); + fn6<S>(); +} diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C new file mode 100644 index 000000000000..a6044c52e7e8 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C @@ -0,0 +1,19 @@ +// PR c++/123536 +// { dg-do compile { target c++11 } } + +int g; + +template<class T> +void +fn1 () +{ + T c = g; + auto l = [] { c++; }; + l(); +} + +void +bar () +{ + fn1<int&> (); +} diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C new file mode 100644 index 000000000000..2299556a4e68 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C @@ -0,0 +1,16 @@ +// PR c++/123536 +// { dg-do compile { target c++11 } } + +template<class T> +void +f () +{ + int& c = T::x; // { dg-error ".x. is not a member of .int." } + auto l = [] { c++; }; // { dg-error ".c. is not captured" } +} + +void +g () +{ + f<int>(); +} diff --git a/gcc/testsuite/g++.dg/template/local11.C b/gcc/testsuite/g++.dg/template/local11.C new file mode 100644 index 000000000000..f75b748caa83 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/local11.C @@ -0,0 +1,18 @@ +// PR c++/123536 + +int g; + +template<class T> +void +fn1 () +{ + T c = g; + struct A { static void f() { c++; } }; + A::f(); +} + +void +bar () +{ + fn1<int&> (); +}
