From 7b8ac81770eded68dde087f913ab269159b49352 Mon Sep 17 00:00:00 2001 From: Daveed Vandevoorde Date: Tue, 3 Nov 2026 00:04:08 +0000 Subject: [PATCH] Template parameter objects of enclosing templates in member template signatures [GH #423] --- src/expr.c | 2 +- src/exprutil.c | 14 ++++- src/exprutil.h | 4 ++ ...-template-param-object-member-template.txt | 21 ++++++++ .../regressions/.gh-423.rto/default.1.1.txt | 1 + .../regressions/.gh-423.rto/default.2.1.txt | 1 + .../regressions/.gh-423.rto/default.3.1.txt | 1 + .../regressions/.gh-423.rto/default.4.1.txt | 1 + tests/tests/regressions/gh-423.sft.cpp | 53 +++++++++++++++++++ 9 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/pending_changes/gh-423-template-param-object-member-template.txt create mode 100644 tests/tests/regressions/.gh-423.rto/default.1.1.txt create mode 100644 tests/tests/regressions/.gh-423.rto/default.2.1.txt create mode 100644 tests/tests/regressions/.gh-423.rto/default.3.1.txt create mode 100644 tests/tests/regressions/.gh-423.rto/default.4.1.txt create mode 100644 tests/tests/regressions/gh-423.sft.cpp diff --git a/src/expr.c b/src/expr.c index 67283dbc3a..9ea6f97565 100644 --- a/src/expr.c +++ b/src/expr.c @@ -54667,7 +54667,7 @@ is TRUE if the expression is the immediate operand of an "&" operator. if (csp != NULL && symbol_is(csp, sk_constant)) { make_sym_constant_operand(csp, result); } else { - make_constant_operand(cp, result); + make_template_param_object_variable_operand(var, result); } /* if */ } else { sym = symbol_for(var); diff --git a/src/exprutil.c b/src/exprutil.c index c9bb061c90..2602d23f40 100644 --- a/src/exprutil.c +++ b/src/exprutil.c @@ -8540,6 +8540,18 @@ allocated in the file scope memory region. template_param_objects->map(ch, vp); sym->variant.variable.ptr = vp; } /* if */ + make_template_param_object_variable_operand(vp, operand); +} /* make_template_param_object_operand */ + + +void make_template_param_object_variable_operand(a_variable_ptr vp, + an_operand *operand) +/* +Make operand a (const) lvalue expression operand for the template parameter +object vp. +*/ +{ + check_assertion(vp->is_template_param_object); /* Making an operand for the object is a use of it and because the object is an inline variable its definition must then be emitted here. The usual symbol reference path does not record that because the object is @@ -8553,7 +8565,7 @@ allocated in the file scope memory region. check_assertion(is_expression_operand(operand)); operand->variant.expression->type = operand->type; (void)make_node_from_operand(operand); -} /* make_template_param_object_operand */ +} /* make_template_param_object_variable_operand */ void make_sym_constant_operand(a_symbol_ptr sym, diff --git a/src/exprutil.h b/src/exprutil.h index bd80890137..9deb1867f5 100644 --- a/src/exprutil.h +++ b/src/exprutil.h @@ -2147,6 +2147,10 @@ extern void make_constant_operand(a_constant *constant, extern void make_sym_constant_operand(a_symbol_ptr sym, an_operand *operand); +extern void make_template_param_object_variable_operand( + a_variable_ptr vp, + an_operand *operand); + extern void make_string_constant_operand(a_constant *constant, an_operand *operand); diff --git a/src/pending_changes/gh-423-template-param-object-member-template.txt b/src/pending_changes/gh-423-template-param-object-member-template.txt new file mode 100644 index 0000000000..145fa63911 --- /dev/null +++ b/src/pending_changes/gh-423-template-param-object-member-template.txt @@ -0,0 +1,21 @@ +[GH #423] +Class-type template parameters of enclosing templates in member templates + +When an enclosing template (e.g., a class template) was instantiated, a +reference to one of its nontype template parameters of class type in the +signature of a member template was replaced by the template parameter +object. When the member template's own template arguments were +substituted, that reference was then treated as a prvalue instead of as a +const lvalue. As a result, taking its address caused deduction to fail, and +other uses (e.g., in a decltype operand) caused a spurious "unexpected +function type" error. That is now fixed. For example, with --c++20: + + struct A { int i = 0; }; + constexpr A a1{1}; + template struct S { + template static auto f(T t) -> decltype(t, &V); + template static auto g(T t) -> decltype(t, V); + }; + using R1 = decltype(S::f(0)); // Previously, no instance matched. + using R2 = decltype(S::g(0)); // Previously, "unexpected function + // type" error. diff --git a/tests/tests/regressions/.gh-423.rto/default.1.1.txt b/tests/tests/regressions/.gh-423.rto/default.1.1.txt new file mode 100644 index 0000000000..32fc82e916 --- /dev/null +++ b/tests/tests/regressions/.gh-423.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-423.rto/default.2.1.txt b/tests/tests/regressions/.gh-423.rto/default.2.1.txt new file mode 100644 index 0000000000..15f97a3e0c --- /dev/null +++ b/tests/tests/regressions/.gh-423.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-423.rto/default.3.1.txt b/tests/tests/regressions/.gh-423.rto/default.3.1.txt new file mode 100644 index 0000000000..e8b472f34d --- /dev/null +++ b/tests/tests/regressions/.gh-423.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-423.rto/default.4.1.txt b/tests/tests/regressions/.gh-423.rto/default.4.1.txt new file mode 100644 index 0000000000..6e33f463c5 --- /dev/null +++ b/tests/tests/regressions/.gh-423.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-423.sft.cpp b/tests/tests/regressions/gh-423.sft.cpp new file mode 100644 index 0000000000..96cb0840ad --- /dev/null +++ b/tests/tests/regressions/gh-423.sft.cpp @@ -0,0 +1,53 @@ +//type:fp +//options:--c++20:--c++20 --g++ --gnu_version=150200:--c++20 --clang --clang_version=210000:--ms_c++20 --microsoft_version=1950 +//remark:class-type template parameter of an enclosing template in a signature + +// When an enclosing template was instantiated, references to a class-type +// template parameter of that template in the signature of a member template +// were replaced by the template parameter object. Substituting the member +// template's own template arguments then turned the object into a prvalue: +// Taking its address made deduction fail, and other uses resulted in a +// function type inconsistent with that of the template. + +struct A { int i = 0; }; +constexpr A a1{1}; + +template constexpr bool is_same = false; +template constexpr bool is_same = true; + +// A member template of a class template. +template struct S { + static constexpr const A *p = &V; + template static constexpr auto f(T t) -> decltype(t, &V) { + return &V; + } + template static auto g(T t) -> decltype(t, V); + template static auto h(T t) -> decltype(t, V.i); +}; +static_assert(is_same::f(0)), const A *>); +static_assert(S::f(0) == S::p); +static_assert(is_same::g(0)), const A &>); +static_assert(is_same::h(0)), const int &>); + +// A generic lambda in a function template. +template constexpr auto mk() { + return [](auto t) -> decltype(t, &V) { return &V; }; +} +static_assert(mk()(0) == mk()(0L)); + +// A member template of a class template partial specialization. +template struct X; +template struct Tag { }; +template struct X, U> { + template static auto f(T t) -> decltype(t, U(), &V); +}; +static_assert(is_same, int>::f(0)), const A *>); + +// The type of the template parameter is deduced. +struct NoConst { + int operator()() { return 0; } +}; +template struct D { + template static auto g(T t) -> decltype(t, F); +}; +static_assert(is_same::g(0)), const NoConst &>);