Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/Changes
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
Version 7.1

10/16/26 [GH #338]
Lambdas that name template parameters in function template declarations

A lambda in the declaration of a function template that names a template
parameter (in the type of a parameter, in a default template argument, in the
condition of Clang's enable_if attribute, ...) is rescanned when the template
arguments are substituted for the template parameters. The template
parameters of most templates were not visible to that rescan, so the names
were reported as undefined and the template could not be used:

template <class T>
void f(char (*)[sizeof([](T y) { return y; }(T{}))]) {}
void g() { char a[4]; f<int>(&a); } // Was an error: identifier "T" is
// undefined
Comment thread
mborland marked this conversation as resolved.

The template parameters are now bound to the template arguments for the
rescan of the lambda, and are put back afterwards. The same applies to the
condition of Clang's enable_if attribute on a function template, which is now
rescanned in the instantiation scope of the template.

This is not done yet for a generic lambda, a lambda with its own template
parameter list, or a lambda that names a template parameter pack. The names
of the template parameters are still reported as undefined in those:

template <class T>
void f(char (*)[sizeof([](auto) { return T(); }(1))]) {}
void g() { char a[4]; f<int>(&a); } // Still an error: identifier "T" is
// undefined


10/21/26 [GH #335]
Type transformations of qualified function types

Expand Down
243 changes: 240 additions & 3 deletions src/il.c
Original file line number Diff line number Diff line change
Expand Up @@ -20500,6 +20500,223 @@ TRUE.
} /* substituted_cast_is_valid */


static a_boolean lambda_tokens_name_template_param(
a_token_cache_ptr tokens,
a_template_param_ptr tpp)
/*
Return TRUE if one of the identifier tokens cached for a lambda (see
scan_lambda in class_decl.c) names the template parameter tpp. The tokens
keep the spelling of a name and not the entity that it was looked up as, so
an identifier that follows "::", "." or "->" (with "template" between them or
not) is a member or a qualified name (::T, NS::T, x.T, p->T,
G::template T<int>), which is never the template parameter, and is skipped.
*/
{
a_boolean found = FALSE;
a_const_token_cache_iterator it;
a_const_token_cache_iterator end = tokens->end();
a_token_kind prev_kind = tok_error;
a_token_kind prev2_kind = tok_error;

if (tpp->param_symbol != NULL && tpp->param_symbol->header != NULL) {
for (it = tokens->begin(); it != end && !found; ++it) {
const a_shared_token &tok = *it;
/* The name of a member or of a qualified name follows the qualifier,
possibly with the keyword "template" between them. */
a_token_kind qualifier_kind = prev_kind == tok_template ?
prev2_kind : prev_kind;
if (tok->is_identifier() &&
qualifier_kind != tok_colon_colon && qualifier_kind != tok_period &&
qualifier_kind != tok_arrow &&
tok->get_symbol_locator()->symbol_header ==
tpp->param_symbol->header) {
found = TRUE;
} /* if */
prev2_kind = prev_kind;
prev_kind = tok->get_kind();
} /* for */
} /* if */
return found;
} /* lambda_tokens_name_template_param */


static a_boolean instantiation_scope_is_active_for_param_list(
a_template_param_ptr template_param_list)
/*
Return TRUE if an instantiation scope for the template whose parameters are
template_param_list is on the scope stack (as for the rescans of a function
template declaration done in template argument deduction and in the
substitution into attributes), so that the names of the template parameters
are found by name lookup. The scope is recognized by its parameter list, so
that the rescan of another template cannot be taken for it.
*/
{
a_scope_depth scope_depth;
a_boolean found = FALSE;

for (scope_depth = depth_scope_stack; scope_depth >= 0; scope_depth--) {
if (scope_is(&scope_stack[scope_depth], sck_template_instantiation) &&
scope_stack[scope_depth].template_decl_info != NULL &&
scope_stack[scope_depth].template_decl_info->parameters ==
template_param_list) {
found = TRUE;
break;
} /* if */
} /* for */
return found;
} /* instantiation_scope_is_active_for_param_list */


typedef struct a_saved_template_param_symbol
*a_saved_template_param_symbol_ptr;
typedef struct a_saved_template_param_symbol {
/* The state of a template parameter symbol that
update_template_param_symbols changes. */
a_symbol_ptr param_symbol;
a_boolean not_visible;
/* When param_symbol->kind == sk_type: */
a_type_ptr type;
/* When param_symbol->kind == sk_constant: */
a_constant_ptr
constant;
/* When param_symbol->kind == sk_class_template: */
a_symbol_ptr argument_template;
a_template_ptr
substituted_param_template;
} a_saved_template_param_symbol;


static a_saved_template_param_symbol_ptr save_template_param_symbols(
a_template_param_ptr template_param_list,
size_t *num_symbols)
/*
Save the state of the symbols of the parameters in template_param_list, so
that restore_template_param_symbols can put them back exactly as they were.
Return the saved state (an array that has *num_symbols entries) and release
it with restore_template_param_symbols.
*/
{
a_template_param_ptr tpp;
a_saved_template_param_symbol_ptr saved;
size_t i = 0;

*num_symbols = 0;
for (tpp = template_param_list; tpp != NULL; tpp = tpp->next) {
(*num_symbols)++;
} /* for */
saved = (a_saved_template_param_symbol_ptr)alloc_general(
*num_symbols * sizeof(a_saved_template_param_symbol));
for (tpp = template_param_list; tpp != NULL; tpp = tpp->next, i++) {
a_symbol_ptr sym = tpp->param_symbol;
a_saved_template_param_symbol_ptr sp = &saved[i];
memset((void*)sp, 0, sizeof(a_saved_template_param_symbol));
sp->param_symbol = sym;
sp->not_visible = sym->template_param_not_visible;
if (sym->kind == (a_symbol_kind)sk_type) {
sp->type = sym->variant.type.ptr;
} else if (sym->kind == (a_symbol_kind)sk_constant) {
sp->constant = sym->variant.constant;
} else {
a_template_symbol_supplement_ptr tssp = sym->variant.template_info;
check_assertion(sym->kind == (a_symbol_kind)sk_class_template);
sp->argument_template = tssp->variant.class_template.argument_template;
sp->substituted_param_template =
tssp->variant.class_template.substituted_param_template;
} /* if */
} /* for */
return saved;
} /* save_template_param_symbols */


static void restore_template_param_symbols(
a_saved_template_param_symbol_ptr saved,
size_t count)
/*
Put the template parameter symbols saved by save_template_param_symbols back
as they were, and release the saved state.
*/
{
size_t i;

for (i = 0; i < count; i++) {
a_saved_template_param_symbol_ptr sp = &saved[i];
a_symbol_ptr sym = sp->param_symbol;
if (sym->kind == (a_symbol_kind)sk_type) {
sym->variant.type.ptr = sp->type;
} else if (sym->kind == (a_symbol_kind)sk_constant) {
sym->variant.constant = sp->constant;
} else {
a_template_symbol_supplement_ptr tssp = sym->variant.template_info;
tssp->variant.class_template.argument_template =
sp->argument_template;
tssp->variant.class_template.substituted_param_template =
sp->substituted_param_template;
} /* if */
sym->template_param_not_visible = sp->not_visible;
} /* for */
free_general(saved, count * sizeof(a_saved_template_param_symbol));
} /* restore_template_param_symbols */


static a_boolean bind_template_params_for_lambda_rescan(
a_lambda_ptr lambda,
a_token_cache_ptr tokens,
a_template_param_ptr template_param_list,
a_template_arg_ptr template_arg_list,
a_saved_template_param_symbol_ptr *p_saved,
size_t *p_num_saved)
/*
If an instantiation scope for template_param_list is active and lambda names
one of the parameters, bind those that have a value in template_arg_list for
the rescan of tokens. *p_saved and *p_num_saved get the state to restore
afterwards (NULL and 0 if nothing is bound). Return FALSE if a named
parameter has no value yet. Generic lambdas and packs are not handled yet.
*/
{
a_template_param_ptr tpp;
a_boolean names_a_parameter = FALSE;
a_boolean names_a_pack = FALSE;
a_boolean all_named_have_values = TRUE;

*p_saved = NULL;
*p_num_saved = 0;
if (!lambda->is_generic && !lambda->has_template_param_list &&
instantiation_scope_is_active_for_param_list(template_param_list)) {
for (tpp = template_param_list; tpp != NULL; tpp = tpp->next) {
if (lambda_tokens_name_template_param(tokens, tpp)) {
names_a_parameter = TRUE;
if (tpp->is_pack) names_a_pack = TRUE;
} /* if */
} /* for */
} /* if */
if (names_a_parameter && !names_a_pack) {
a_saved_template_param_symbol_ptr saved;
size_t num_saved;
saved = save_template_param_symbols(template_param_list, &num_saved);
/* Hide every parameter first, so that one that has no value in
template_arg_list stays hidden whatever state its symbol was in. */
for (tpp = template_param_list; tpp != NULL; tpp = tpp->next) {
tpp->param_symbol->template_param_not_visible = TRUE;
} /* for */
update_template_param_symbols(template_param_list, template_arg_list,
/*partial_argument_list=*/TRUE);
for (tpp = template_param_list; tpp != NULL; tpp = tpp->next) {
if (tpp->param_symbol->template_param_not_visible &&
lambda_tokens_name_template_param(tokens, tpp)) {
all_named_have_values = FALSE;
} /* if */
} /* for */
if (all_named_have_values) {
*p_saved = saved;
*p_num_saved = num_saved;
} else {
restore_template_param_symbols(saved, num_saved);
} /* if */
} /* if */
return all_named_have_values;
} /* bind_template_params_for_lambda_rescan */


static an_expr_node_ptr copy_template_param_lambda_expr(
an_expr_node_ptr expr,
a_template_arg_ptr template_arg_list,
Expand All @@ -20521,6 +20738,10 @@ copy_template_param_expr for the parameter descriptions.
a_lambda_ptr lambda = expr->variant.init.source.lambda;
a_type_ptr closure_class = lambda->closure_class;
a_token_cache_ptr tokens;
a_saved_template_param_symbol_ptr
saved_param_symbols = NULL;
size_t num_saved_param_symbols = 0;
a_boolean params_have_decl_scope;

/* First, do a preliminary substitution on the lambda's routine type so that
any SFINAE-able substitution failure is reported as a deduction failure
Expand All @@ -20531,10 +20752,22 @@ copy_template_param_expr for the parameter descriptions.
copy_error, ctws_state);
if (*copy_error) goto done;
tokens = cached_lambdas->get(closure_class);
params_have_decl_scope = template_param_list != NULL &&
template_param_list->cache.decl_info != NULL &&
template_param_list->param_symbol != NULL;
if (tokens == NULL) {
/* No cached tokens were recorded for this lambda (this can happen in
severe error cases). Substitution cannot proceed. */
subst_fail(*copy_error);
} else if (!params_have_decl_scope && template_param_list != NULL &&
!bind_template_params_for_lambda_rescan(
lambda, tokens,
template_param_list,
template_arg_list,
&saved_param_symbols,
&num_saved_param_symbols)) {
/* The lambda names a template parameter that has no value yet. */
subst_fail(*copy_error);
} else {
an_operand result_operand;
an_expr_stack_entry expr_stack_entry;
Expand All @@ -20554,9 +20787,7 @@ copy_template_param_expr for the parameter descriptions.
expr_stack_entry.suppress_diagnostics = TRUE;
expr_stack_entry.possible_rescan_context = TRUE;
push_lexical_state_stack();
if (template_param_list != NULL &&
template_param_list->cache.decl_info != NULL &&
template_param_list->param_symbol != NULL) {
if (params_have_decl_scope) {
/* Make the template parameters being substituted visible during the
rescan so that names that refer to them can be resolved. The scope
is pushed reusing the original declaration scope number so that the
Expand Down Expand Up @@ -20643,6 +20874,12 @@ copy_template_param_expr for the parameter descriptions.
if (pushed_template_decl_scope) {
pop_scope();
} /* if */
if (saved_param_symbols != NULL) {
/* Put back the template parameters that
bind_template_params_for_lambda_rescan bound. */
restore_template_param_symbols(saved_param_symbols,
num_saved_param_symbols);
} /* if */
pop_lexical_state_stack();
pop_expr_stack();
restore_expr_stack(saved_expr_stack);
Expand Down
8 changes: 8 additions & 0 deletions src/overload.c
Original file line number Diff line number Diff line change
Expand Up @@ -5641,10 +5641,18 @@ is used in the constraint, its evaluation will fail.
a_template_ptr templ = rp->assoc_template;
check_assertion(templ != NULL);
init_ctws_state(&ctws_state);
/* Push an instantiation scope for the template, as the substitution
into the signature does (see the call of
push_instantiation_scope_for_rescan in
wrapup_function_template_argument_deduction's caller), so that the
names of its template parameters are found if the substitution
rescans a lambda in the condition. */
push_instantiation_scope_for_rescan(symbol_for(templ));
expr = copy_expr_with_substitutions(expr, t_args,
templ_params_of(symbol_for(templ)),
CTWS_NO_OPTIONS, &failed,
&ctws_state);
pop_instantiation_scope_for_rescan();
if (failed) {
/* If substitution failed, the function is discarded as a candidate.
No need to try another attribute. */
Expand Down
2 changes: 0 additions & 2 deletions tests/expectations/edg_x86_64/imported.log
Original file line number Diff line number Diff line change
Expand Up @@ -7122,14 +7122,12 @@ gnu/cpp/cpp2a/lambda-generic2.sft.cpp:fn:-DTEST_NUMBER=1 --c++17:FAIL:
gnu/cpp/cpp2a/lambda-generic3.sft.cpp:fn:-DTEST_NUMBER=1 --c++17:FAIL:
gnu/cpp/cpp2a/lambda-generic7.sft.cpp:fn:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/lambda-targ1.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/lambda-targ4.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/lambda-targ7.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:ABORT:
gnu/cpp/cpp2a/lambda-targ8.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/lambda-this3.sft.cpp:fp:-DTEST_NUMBER=1 --c++17:FAIL:
gnu/cpp/cpp2a/lambda-uneval11.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/lambda-uneval13.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/lambda-uneval3.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/lambda-uneval7.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/lambda-uneval8.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
gnu/cpp/cpp2a/linkage-spec1.sft.cpp:fn:-DTEST_NUMBER=1 --c++11:FAIL:
gnu/cpp/cpp2a/nontype-class17.sft.cpp:fn:-DTEST_NUMBER=1 --c++20:FAIL:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=1 --clang --clang_version=210000 --c++20 Test_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=2 --clang --clang_version=210000 --c++23 Test_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fe_only -DTEST_NUMBER=1 --c++20 Test_name.c
"Test_name.c", line 16: error: identifier "T" is undefined
void f(char (*)[sizeof([](auto x){ return T(x); }(1))]) {}
^
detected during instantiation of function "lambda [](auto)->auto [with <auto-1>=int]" at line 17

"Test_name.c", line 16: error: identifier "T" is undefined
void f(char (*)[sizeof([](auto x){ return T(x); }(1))]) {}
^
detected during instantiation of function "lambda [](auto)->auto [with <auto-1>=int]" at line 17

"Test_name.c", line 17: error: no instance of function template "f" matches the argument list
argument types are: (char (*)[4])
void use() { char a[sizeof(int)]; f<int>(&a); }
^
"Test_name.c", line 16: note: function template "f" does not match because argument #1 does not match parameter
void f(char (*)[sizeof([](auto x){ return T(x); }(1))]) {}
^

3 errors detected in the compilation of "Test_name.c".
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Matches for "line 16: error: identifier "T" is undefined":
<-----------------------------------------------------------------------------
line 16: error: identifier "T" is undefined
----------------------------------------------------------------------------->
<-----------------------------------------------------------------------------
line 16: error: identifier "T" is undefined
----------------------------------------------------------------------------->
Matches for "line 17: error: no instance of function template "f" matches the argument list":
<-----------------------------------------------------------------------------
line 17: error: no instance of function template "f" matches the argument list
----------------------------------------------------------------------------->
Matches for "\A(?![\s\S]*line (?!1[67]\b)\d+: error)":
<-----------------------------------------------------------------------------

----------------------------------------------------------------------------->
Loading
Loading