From 946defd2ed92baff1f4ea81d92ad1872c82682cf Mon Sep 17 00:00:00 2001 From: Daveed Vandevoorde Date: Tue, 3 Nov 2026 00:03:24 +0000 Subject: [PATCH] decltype of template parameters in requires-expression parameter types [GH #462] --- src/declarator.c | 7 ++ src/exprutil.c | 19 +++- .../gh-462-requires-expr-param-decltype.txt | 24 +++++ .../regressions/.gh-462.rto/default.1.1.txt | 1 + .../regressions/.gh-462.rto/default.2.1.txt | 1 + .../regressions/.gh-462.rto/default.3.1.txt | 1 + .../regressions/.gh-462.rto/default.4.1.txt | 1 + tests/tests/regressions/gh-462.sft.cpp | 96 +++++++++++++++++++ 8 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/pending_changes/gh-462-requires-expr-param-decltype.txt create mode 100644 tests/tests/regressions/.gh-462.rto/default.1.1.txt create mode 100644 tests/tests/regressions/.gh-462.rto/default.2.1.txt create mode 100644 tests/tests/regressions/.gh-462.rto/default.3.1.txt create mode 100644 tests/tests/regressions/.gh-462.rto/default.4.1.txt create mode 100644 tests/tests/regressions/gh-462.sft.cpp diff --git a/src/declarator.c b/src/declarator.c index f373caf99b..8054f3a87a 100644 --- a/src/declarator.c +++ b/src/declarator.c @@ -3329,6 +3329,13 @@ an error if a default argument expression is encountered. *new_type_ptr, (a_routine_ptr)NULL); scope_stack_top().decl_parse_state = state; must_pop_function_prototype_scope = !state->for_requires_expr_params; + if (state->for_requires_expr_params && is_template_dependent_context()) { + /* Like its requirements (see scan_requires_expr), the parameter + declarations of a requires-expression in a template-dependent + context undergo substitution: Make sure the rescan information + needed for that is recorded. */ + scope_stack_top().in_template_deduction_context = TRUE; + } /* if */ if (is_constructor && is_template_dependent_context()) { /* A constructor in a template-dependent context may be used for deduction even if it isn't a template itself, because of C++17 diff --git a/src/exprutil.c b/src/exprutil.c index c9bb061c90..e647b3e66a 100644 --- a/src/exprutil.c +++ b/src/exprutil.c @@ -4917,6 +4917,7 @@ in *bound_function_selector. an_expr_node_ptr expr_copy = NULL; a_boolean rescanned_case = FALSE; a_boolean is_template_param_object = FALSE; + a_boolean is_template_param_lvalue = FALSE; a_constant_ptr constant = local_constant(); a_constant_ptr alloc_con = NULL; an_expr_rescan_info_entry_ptr eriep; @@ -4997,6 +4998,21 @@ in *bound_function_selector. } else { /* The result is a constant, so return a constant operand. */ make_constant_operand(cp, operand); + if (constant_is(cp, ck_template_param) && + is_operation_node(orig_expr) && + node_operator_is(orig_expr, eok_lvalue) && + orig_expr->variant.operation.operands == expr) { + /* A reference to a C++20 nontype template parameter of class + type that is still a template parameter after substitution + (e.g., when an enclosing template is instantiated): Keep it + an lvalue, as it was originally (see make_sym_constant_operand), + so that it can later be identified as an id-expression + designating the template parameter object. */ + change_template_param_constant_operand_to_lvalue( + operand, + !is_template_dependent_type(cp->type)); + is_template_param_lvalue = TRUE; + } /* if */ } /* if */ } else { /* Return an expression operand. */ @@ -5020,7 +5036,8 @@ in *bound_function_selector. /* Normal case: restore everything. */ restore_operand_info_from_expr_rescan_info_entry(operand, eriep); } /* if */ - if (is_template_param_object && orig_expr->extra.rescan_info != NULL) { + if ((is_template_param_object || is_template_param_lvalue) && + orig_expr->extra.rescan_info != NULL) { /* The rescan information on the template parameter constant was saved before the reference was identified as an id-expression (possibly parenthesized). That is recorded on the node that makes diff --git a/src/pending_changes/gh-462-requires-expr-param-decltype.txt b/src/pending_changes/gh-462-requires-expr-param-decltype.txt new file mode 100644 index 0000000000..16971a32f1 --- /dev/null +++ b/src/pending_changes/gh-462-requires-expr-param-decltype.txt @@ -0,0 +1,24 @@ +[GH #462] +decltype of template parameters in requires-expression parameter types + +When a requires-expression appeared in a template other than a concept +definition (e.g., in a variable template initializer or a function template +body), decltype applied to a template parameter in the type of one of its +parameters was not substituted correctly. The parameter then got an +unusable type, so that any requirement using it in an operation (e.g., a +call) was considered unsatisfied. Similarly, in a member template of a class +template, decltype applied to a class-type template parameter of the class +template produced the const type of the template parameter object (rather +than the declared type of the template parameter) once the class template +was instantiated. That is now fixed. For example, with --c++20: + + struct A { int operator()() { return 0; } }; + template + constexpr bool b = requires(decltype(F) f) { f(); }; + static_assert(b); // Previously failed. + template struct S { + template static constexpr bool m() { + return requires(decltype(F) f) { f(); }; + } + }; + static_assert(S::m<1>()); // Previously failed. diff --git a/tests/tests/regressions/.gh-462.rto/default.1.1.txt b/tests/tests/regressions/.gh-462.rto/default.1.1.txt new file mode 100644 index 0000000000..32fc82e916 --- /dev/null +++ b/tests/tests/regressions/.gh-462.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++20 Test_name.c diff --git a/tests/tests/regressions/.gh-462.rto/default.2.1.txt b/tests/tests/regressions/.gh-462.rto/default.2.1.txt new file mode 100644 index 0000000000..15f97a3e0c --- /dev/null +++ b/tests/tests/regressions/.gh-462.rto/default.2.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=2 --c++20 --g++ --gnu_version=150200 Test_name.c diff --git a/tests/tests/regressions/.gh-462.rto/default.3.1.txt b/tests/tests/regressions/.gh-462.rto/default.3.1.txt new file mode 100644 index 0000000000..e8b472f34d --- /dev/null +++ b/tests/tests/regressions/.gh-462.rto/default.3.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=3 --c++20 --clang --clang_version=210000 Test_name.c diff --git a/tests/tests/regressions/.gh-462.rto/default.4.1.txt b/tests/tests/regressions/.gh-462.rto/default.4.1.txt new file mode 100644 index 0000000000..6e33f463c5 --- /dev/null +++ b/tests/tests/regressions/.gh-462.rto/default.4.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=4 --ms_c++20 --microsoft_version=1950 Test_name.c diff --git a/tests/tests/regressions/gh-462.sft.cpp b/tests/tests/regressions/gh-462.sft.cpp new file mode 100644 index 0000000000..728ae80adc --- /dev/null +++ b/tests/tests/regressions/gh-462.sft.cpp @@ -0,0 +1,96 @@ +//type:fp +//options:--c++20:--c++20 --g++ --gnu_version=150200:--c++20 --clang --clang_version=210000:--ms_c++20 --microsoft_version=1950 +//remark:decltype of a template parameter in a requires-expression parameter + +// The parameter declarations of a requires-expression appearing outside of a +// concept definition did not record the information needed to substitute +// decltype applied to a template parameter. The substituted parameter type +// was therefore unusable, and any requirement using the parameter in an +// operation was considered unsatisfied. In a member template of a class +// template, decltype applied to a class-type template parameter of the +// enclosing class template also lost its id-expression form when the class +// template was instantiated, so the parameter got the const type of the +// template parameter object. + +template constexpr bool is_same = false; +template constexpr bool is_same = true; + +struct NoConst { + int operator()() { return 0; } +}; + +// F is a const lvalue (a template parameter object), but decltype(F) is the +// declared type of the template parameter, which is not const. +template +inline constexpr bool const_callable_v = requires { F(); }; +template +inline constexpr bool non_const_callable_v = + requires(decltype(F) f) { f(); }; +static_assert(!const_callable_v); +static_assert(non_const_callable_v); + +// A template parameter declared with the class type. +template +constexpr bool typed_v = requires(decltype(F) f) { f(); }; +static_assert(typed_v); + +// The parameter has exactly the declared type. +template +constexpr bool exact_type_v = + requires(decltype(F) f) { requires is_same; }; +static_assert(exact_type_v); + +// Declarators built on the decltype type. +template +constexpr bool ref_v = requires(decltype(F) &f) { f(); }; +static_assert(ref_v); +template +constexpr bool ptr_v = requires(decltype(F) *f) { (*f)(); }; +static_assert(ptr_v); + +// The parenthesized form denotes the const template parameter object. +template +constexpr bool paren_v = requires(decltype((F)) f) { f(); }; +static_assert(!paren_v); + +// A nontype template parameter of non-class type. +template +constexpr bool int_v = requires(decltype(N) n) { n + 1; }; +static_assert(int_v<42>); + +// A requires-expression in a function template body. +template constexpr bool in_function() { + return requires(decltype(F) f) { f(); }; +} +static_assert(in_function()); + +// A requires-expression in a member of a class template. +template struct S { + static constexpr bool value = requires(decltype(F) f) { f(); }; + template static constexpr bool member_template() { + return requires(decltype(F) f, decltype(I) i) { f() + i; }; + } + template static constexpr bool member_exact_type() { + return requires(decltype(F) f) { + requires is_same; + }; + } + template static constexpr bool member_paren() { + return requires(decltype((F)) f) { f(); }; + } + template static constexpr bool out_of_class(); +}; +template template +constexpr bool S::out_of_class() { + return requires(decltype(F) f) { f(); }; +} +static_assert(S::value); +static_assert(S::member_template<1>()); +static_assert(S::member_exact_type<1>()); +static_assert(!S::member_paren<1>()); +static_assert(S::out_of_class<1>()); + +// This already worked in a concept definition. +template +concept NonConstCallable = requires(decltype(F) f) { f(); }; +static_assert(NonConstCallable);