On 6/7/22 14:25, Patrick Palka wrote:
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.
Never mind, then.
Here's what I have so far, which survives bootstrap and regtest.
OK.
-- >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), "");