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?

> 
> This minimal patch makes us clear cp_unevaluated_operand when
> substituting into the template arguments of an alias template-id, as in
> tsubst_aggr_type.
> 
> (A few lines below we also substitute into the template arguments of a
> class-scope typedef, during which we arguably also should clear
> cp_unevaluated_operand, but I wasn't able to come up with a testcase for
> which this mattered, because if we've named a class-scope typedef, then
> at some point tsubst_aggr_type must have already substituted the class
> scope, which performs the same substitution with cp_unevaluated_operand
> cleared.)
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk?
> 
>       PR c++/101906
> 
> gcc/cp/ChangeLog:
> 
>       * pt.cc (tsubst): Clear cp_unevaluated_operand when substituting
>       the template arguments of an alias template specialization.
> 
> gcc/testsuite/ChangeLog:
> 
>       * g++.dg/cpp0x/alias-decl-75.C: New test.
>       * g++.dg/cpp0x/alias-decl-75a.C: New test.
> ---
>  gcc/cp/pt.cc                                |  6 +++++-
>  gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C  | 15 +++++++++++++++
>  gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C | 16 ++++++++++++++++
>  3 files changed, 36 insertions(+), 1 deletion(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 7697615ac64..c7116849551 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -15545,7 +15545,11 @@ tsubst (tree t, tree args, tsubst_flags_t complain, 
> tree in_decl)
>         /* DECL represents an alias template and we want to
>            instantiate it.  */
>         tree tmpl = most_general_template (DECL_TI_TEMPLATE (decl));
> -       tree gen_args = tsubst (DECL_TI_ARGS (decl), args, complain, in_decl);
> +       tree gen_args;
> +         {
> +           cp_evaluated ev;
> +           gen_args = tsubst (DECL_TI_ARGS (decl), args, complain, in_decl);
> +         }
>         r = instantiate_alias_template (tmpl, gen_args, complain);
>       }
>        else if (DECL_CLASS_SCOPE_P (decl)
> diff --git a/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C 
> b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C
> new file mode 100644
> index 00000000000..c6176751283
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C
> @@ -0,0 +1,15 @@
> +// PR c++/101906
> +// { 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/cpp0x/alias-decl-75a.C 
> b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C
> new file mode 100644
> index 00000000000..ce08a84f6d9
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C
> @@ -0,0 +1,16 @@
> +// PR c++/101906
> +// Similar to alias-decl-75.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>);
> -- 
> 2.35.1.607.gf01e51a7cf
> 
> 

Reply via email to