W.F. W.F. - 1 month ago 7
C++ Question

Are placeholders types of non-type template parameters interchangeable in case of template template parameter

Consider a simple example:

int x;

template <template <auto> class TT>
struct Foo {
void foo() {
TT<(x)> tt;
static_cast<void>(tt);
}
};

template <decltype(auto)>
struct Bar { };


int main() {
Foo<Bar> foobar;
foobar.foo();
}


[clang] seems to deal with the idea of
decltype(auto)
placeholder despite the use of
auto
in template template parameter declaration without a problem.

[gcc] on the other hand - not very well:


prog.cc:6:13: error: the value of 'x' is not usable in a constant expression





As usually - which behaviour is expected according to standard? Or maybe everything is possible and the code is ill-formed (this time I suppose not but cannot rule it out definitively)?

PS. Sorry for breaking one of the compilers again ;)

Answer Source

The original answer here had Foo<Bar> ill-formed, I actually now think it's well-formed. But ultimately, clang bug based.


I actually think even Foo<Bar> is ill-formed. The new rules, following P0522 are that:

A template-argument matches a template template-parameter P when P is at least as specialized as the template-argument A

where:

A template template-parameter P is at least as specialized as a template template-argument A if, given the following rewrite to two function templates, the function template corresponding to P is at least as specialized as the function template corresponding to A according to the partial ordering rules for function templates ([temp.func.order]). Given an invented class template X with the template parameter list of A (including default arguments):

  • Each of the two function templates has the same template parameters, respectively, as P or A.
  • Each function template has a single function parameter whose type is a specialization of X with template arguments corresponding to the template parameters from the respective function template where, for each template parameter PP in the template parameter list of the function template, a corresponding template argument AA is formed. If PP declares a parameter pack, then AA is the pack expansion PP... ([temp.variadic]); otherwise, AA is the id-expression PP.

If the rewrite produces an invalid type, then P is not at least as specialized as A.

Which means that to verify if Foo<Bar> itself is okay, we synthesize:

template <decltype(auto) I> struct X;

template <auto I>           void __f(X<I> ); // P
template <decltype(auto) I> void __f(X<I> ); // A

All the types here are valid (so the last statement doesn't apply). Now, typically when we do partial ordering it's in the context of either overload resolution or picking a class template specialization, in which case what we're looking for is the "more specialized" function template, where F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.

But in this context, we don't care about which is more specialized. We only need P to be at least as specialized as A. All that means that deduction has to succeed from A to P. So if we synthesize some unique type U with some value V, can we deduce X<I> from X<V>? Yes. Hence, P is at least as specialized as A, so the template-argument Bar matches the template-parameter TT.


Now, passing that point, I'd say this a clang bug. The template template-parameter is template <auto>, which is what we should use to validate the expression. With a non-type template parameter auto, we'd try to use x as a value - but x isn't a valid constant expression, so this should fail. clang appears to be using template <decltype(auto) > directly - which I'm not sure is valid.

That said, I'm not sure this case has even been considered - I don't see any wording one way or the other and it's worth raising an issue.