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), "");

Reply via email to