On Wed, 23 Mar 2022, Jason Merrill wrote: > On 3/22/22 14:31, Patrick Palka wrote: > > On Tue, 22 Mar 2022, Patrick Palka wrote: > > > > > Here we're neglecting to clear cp_unevaluated_operand when substituting > > > into the arguments of the alias template-id skip<(T(), 0), T> with T=A, > > > which means cp_unevaluated_operand remains set during mark_used for > > > A::A() and so we never synthesize it. Later constant evaluation for > > > the substituted template argument (A(), 0) (during coerce_template_parms) > > > fails with "'constexpr A::A()' used before its definition" since it was > > > never synthesized. > > > > It occurred to me to check the case where 'skip' is a function/variable > > template instead of an alias template, and unfortunately seems we run > > into the same issue: > > > > template<int, class T> T skip(); // Function template > > // template<int, class T> T skip; // Variable template > > > > template<class T> > > constexpr unsigned sizeof_() { > > return sizeof(skip<(T(), 0), T>()); > > // return sizeof(skip<(T(), 0), T>); > > } > > > > struct A { > > int m = -1; > > }; > > > > static_assert(sizeof_<A>() == sizeof(A), ""); > > > > <stdin>: In instantiation of ‘constexpr unsigned int sizeof_() [with T = > > A]’: > > <stdin>:14:25: required from here > > <stdin>:6:34: error: ‘constexpr A::A()’ used before its definition > > > > We can fix this similarly by clearing cp_unevaluated_operand when > > substituting into the arguments of a TEMPLATE_ID_EXPR, but now I'm > > worried this cp_unevaluated_operand business might not be the best > > approach (despite it being consistent with what tsubst_aggr_type does). > > > > Maybe instantiate_cx_fn_r should be responsible for making sure A::A() > > gets synthesized? > > Or cxx_eval_call_expression, but just as a workaround: > manifestly-constant-evaluated expressions are evaluated even in an unevaluated > operand, so I think adjusting cp_unevaluated_operand is correct. > > Perhaps tsubst_template_args should use cp_evaluated,
Makes sense. > and places that use plain tsubst for substituting template args should > use it instead? Even though tsubst already uses tsubst_template_args to substitute TREE_VEC? AFAICT this change would have no effect except when args is NULL_TREE, in which case tsubst exits early but tsubst_template_args doesn't. Here's what I have so far, which survives bootstrap and regtest. -- >8 -- Subject: [PATCH] c++: template-id arguments are evaluated [PR101906] Here we're neglecting to clear cp_unevaluated_operand when substituting into the arguments of the alias template-id skip<(T(), 0), T> with T=A, which means cp_unevaluated_operand remains set during mark_used for A::A() and so we never synthesize it. Later constant evaluation for the substituted template argument (A(), 0) (during coerce_template_parms) fails with "'constexpr A::A()' used before its definition" since it was never synthesized. This doesn't happen with a class template because tsubst_aggr_type clears cp_unevaluated_operand during substitution thereof. But since template arguments are generally manifestly constant-evaluated, which in turn are evaluated even in an unevaluated operand, we should be clearing cp_unevaluated_operand more broadly whenever substituting any set of template arguments. Thus this patch makes us clear cp_unevaluated_operand during tsubst_template_args. PR c++/101906 gcc/cp/ChangeLog: * pt.cc (tsubst_template_args): Set cp_evaluated here. (tsubst_aggr_type): Not here. gcc/testsuite/ChangeLog: * g++.dg/template/evaluated1.C: New test. * g++.dg/template/evaluated1a.C: New test. * g++.dg/template/evaluated1b.C: New test. * g++.dg/template/evaluated1c.C: New test. --- gcc/cp/pt.cc | 6 +++--- gcc/testsuite/g++.dg/template/evaluated1.C | 17 +++++++++++++++++ gcc/testsuite/g++.dg/template/evaluated1a.C | 16 ++++++++++++++++ gcc/testsuite/g++.dg/template/evaluated1b.C | 17 +++++++++++++++++ gcc/testsuite/g++.dg/template/evaluated1c.C | 17 +++++++++++++++++ 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 gcc/testsuite/g++.dg/template/evaluated1.C create mode 100644 gcc/testsuite/g++.dg/template/evaluated1a.C create mode 100644 gcc/testsuite/g++.dg/template/evaluated1b.C create mode 100644 gcc/testsuite/g++.dg/template/evaluated1c.C diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index ee7d2c935cc..7fe1c7653aa 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -13475,6 +13475,9 @@ tsubst_template_args (tree t, tree args, tsubst_flags_t complain, tree in_decl) if (t == error_mark_node) return error_mark_node; + /* In "sizeof(X<I>)" we need to evaluate "I". */ + cp_evaluated ev; + len = TREE_VEC_LENGTH (t); elts = XALLOCAVEC (tree, len); @@ -13709,9 +13712,6 @@ tsubst_aggr_type (tree t, tree context; tree r; - /* In "sizeof(X<I>)" we need to evaluate "I". */ - cp_evaluated ev; - /* First, determine the context for the type we are looking up. */ context = TYPE_CONTEXT (t); diff --git a/gcc/testsuite/g++.dg/template/evaluated1.C b/gcc/testsuite/g++.dg/template/evaluated1.C new file mode 100644 index 00000000000..41845c65acb --- /dev/null +++ b/gcc/testsuite/g++.dg/template/evaluated1.C @@ -0,0 +1,17 @@ +// PR c++/101906 +// Verify the template arguments of an alias template-id are evaluated even +// in an unevaluated context. +// { dg-do compile { target c++11 } } + +template<int, class T> using skip = T; + +template<class T> +constexpr unsigned sizeof_() { + return sizeof(skip<(T(), 0), T>); +} + +struct A { + int m = -1; +}; + +static_assert(sizeof_<A>() == sizeof(A), ""); diff --git a/gcc/testsuite/g++.dg/template/evaluated1a.C b/gcc/testsuite/g++.dg/template/evaluated1a.C new file mode 100644 index 00000000000..78286871004 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/evaluated1a.C @@ -0,0 +1,16 @@ +// PR c++/101906 +// Like unevaluated1.C, but where the unevaluated context is a +// constraint instead of sizeof. +// { dg-do compile { target c++20 } } + +template<int> using voidify = void; + +template<class T> +concept constant_value_initializable + = requires { typename voidify<(T(), 0)>; }; + +struct A { + int m = -1; +}; + +static_assert(constant_value_initializable<A>); diff --git a/gcc/testsuite/g++.dg/template/evaluated1b.C b/gcc/testsuite/g++.dg/template/evaluated1b.C new file mode 100644 index 00000000000..7994065ac86 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/evaluated1b.C @@ -0,0 +1,17 @@ +// PR c++/101906 +// Like unevaluated1.C, but using a function template instead of an +// alias template. +// { dg-do compile { target c++14 } } + +template<int, class T> T skip(); + +template<class T> +constexpr unsigned sizeof_() { + return sizeof(skip<(T(), 0), T>()); +} + +struct A { + int m = -1; +}; + +static_assert(sizeof_<A>() == sizeof(A), ""); diff --git a/gcc/testsuite/g++.dg/template/evaluated1c.C b/gcc/testsuite/g++.dg/template/evaluated1c.C new file mode 100644 index 00000000000..15c55821c01 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/evaluated1c.C @@ -0,0 +1,17 @@ +// PR c++/101906 +// Like unevaluated1b.C, but using a variable template instead of a +// function template. +// { dg-do compile { target c++14 } } + +template<int, class T> T skip; + +template<class T> +constexpr unsigned sizeof_() { + return sizeof(skip<(T(), 0), T>); +} + +struct A { + int m = -1; +}; + +static_assert(sizeof_<A>() == sizeof(A), ""); -- 2.36.1.299.gab336e8f1c