18

Consider the following program:

template<template<typename ...> class>
struct foo {};

template<template<typename> class C>
struct foo<C> {};

int main() {}

Clang rejects it with error:

class template partial specialization does not specialize any template argument

even in latest clang 7.0 HEAD, see demo here. However, gcc accepts it.

Refer to [temp.class.spec] where the rules of partial specialization are stated, I couldn't find anything that prohibits the partial specialization of this template. Especially, the specialization is indeed more specialized, the error message looks incorrect.

EDIT:

However, gcc's behavior is also abnormal, consider the following program:

#include <iostream>

template<template<typename ...> class>
struct foo { void show() { std::cout << "Primary.\n"; } };

template<template<typename> class C>
struct foo<C> { void show() { std::cout << "Specialized.\n"; } };

template<class...> struct bar {};

int main() {
    foo<bar> f;
    f.show();
}

It turns out that gcc uses the specialized version in this case, see here.

Now I want to ask:

  • is this kind of partial specialization allowed by standard ?

  • which compiler is correct ? ( one/all/none of them ? )

llllllllll
  • 16,169
  • 4
  • 31
  • 54
  • If such specialization is valid then instantiation of `foo` with `template struct bar {};` is ambiguous, isn't it? – Nikita Kniazev Feb 20 '18 at 22:18
  • 1
    @NikitaKniazev For me, this is not ambiguous, it should be the primary `foo` being used. But it seems even more complicated, see the strange behavior of gcc [here](https://wandbox.org/permlink/2OjOulfvDjGJ0CPo). It unconditionally uses the specialized version... – llllllllll Feb 20 '18 at 22:49
  • 1
    About your your ecample `foo f; f.show()`... observe that print "specialized" if you compile C++17, "primary" if you compile C++14; something is changed in C++17 about template-template matching (but I'm not saying g++ is right... I'm really, really confused). – max66 Feb 20 '18 at 23:16
  • @Walter clang 5.0 and clang 7.0 HEAD. The live demo is in question. – llllllllll Feb 20 '18 at 23:23
  • @max66 I don't how to explain... But I think if it's ill-formed, there should be somewhere in the standard stating about it... I can't find anything. – llllllllll Feb 20 '18 at 23:25
  • Adding `-fno-new-ttp-matching` will fix GCC 7.1+ behavior in C++17 mode. – Nikita Kniazev Feb 21 '18 at 18:46
  • @liliscent Could you give us more info about the context in which you're trying to use this partial specialization? Some code that would benefit from it in practice? Since this is no longer valid in C++17, I'm curious about how much impact this may have. – bogdan Feb 21 '18 at 22:04
  • @bogdan I don't think this strange specialization will have impact in practice. Anyway, from my personal experience, template template parameter is usually used as part of another template parameter list, like container. In such typical cases, specialization can be done by those additional parameters, not the template template parameter itself. And clang has never supported this code, not just after C++17. – llllllllll Feb 21 '18 at 22:42

3 Answers3

2

This finaly seems to be a GCC bug in the implementation of the new C++ template template argument deduction partial support for this feature.

To determine if a partial specialization is more specialized than the class template, partial ordering is applied to 2 corresponding synthetized functions:

//template class:
template<template<class...>class P> void f_foo0(foo<P>);

//Partial specialization
template<template<class P> class P> void f_foo_partial0(foo<P>);

Then it is attempted to perform template argument deduction, by calling each functions with an argument corresponding to the other function parameter (see [temp.func.order])

template<class P> struct Atype{};
template<class ...P> struct ATypePack{};

//Is f_foo_partial at least as specialized as f_foo?
f_foo(foo<AType>{});

//Is f_foo at least as specialized as f_foo_partial?
f_foo_partial(foo<ATypePack>{});

If template argument succeed for (1) then f_foo_partial is at least as specialized as f_foo. If template argument succeed for (2) then f_foo is at least as specialized as f_foo_partial.( see [temp.deduct.partial]). Then if only one is at least as specialized as the other, then it is the one which is more specialized.

So to check if template argument is deductible, then deduction of template argument from a type is perfomed.

Then to perform this matching, the introduced rule in C++17 is applied [temp.arg.template]/3:

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

And [temp.arg.template]/4 specify that this ordering will be performed similarily to the preceding case using these invented two functions:

template<class...> struct X{};
//for the argument
template<class...P> void f_targ(X<P...>);

//Partial specialization
template<class P> void f_tparam(X<P>);

struct Atype{};
struct ATypePack{};

//Is template template parameter at least as specialized template template arg?
f_targ(X<AType>{});

//Is template template arg at least as specialized as template template parameter?
f_tparam(X<ATypePack>{});
  • for (1) template argument succeed the deduced argument for ...P`` isAType`.

  • for (2) there is a special rule, that applies only in the case of template partial ordering [temp.deduct.type]/9.2:

[...] During partial ordering, if Ai was originally a pack expansion:

  • if P does not contain a template argument corresponding to Ai then Ai is ignored;

  • otherwise, if Pi is not a pack expansion, template argument deduction fails.

Here Ai is ATypePack and Pi is the P in the function argument of template<class P> void p_foo_partial(foo<P>).

So due to this rule cited in bold, template argument deduction fails for (2), so template template parameter "class P" is more specialized than its argument. So the call f_foo_partial(foo<ATypePack>{}) is well formed.

On the other hand the same rules, the call to f_foo(AType{}) is well formed because of [temp.arg.temp]/3:

[...] If P contains a parameter pack, then A also matches P if each of A's template parameters matches the corresponding template parameter in the template-parameter-list of P.[...]

so f_foo_partial is not more specialized than f_foo, so template<class<class > class C> struct foo<C>; is not a partial specialization of template foo.

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • 1
    The synthesized function templates used for partial ordering should be `template – bogdan Feb 21 '18 at 10:35
  • @bogdan I am working on it, the result will be the same, there is one more level of inference... This will be too long! – Oliv Feb 21 '18 at 10:42
  • The new rule from P0522 makes `f_foo` at least as specialized as `f_foo_partial` (`f_foo_partial(foo{})` succeeds under the new rule). On the other hand, `f_foo_partial` is at least as specialized as `f_foo` because of the rest of [temp.arg.template]/3, which was carried over from C++14. – bogdan Feb 21 '18 at 11:28
  • @bodgan I thought the rule [temp.deduct.type]/9.2 would apply here. Why is not the case? It says that during partial ordering, a pack argument can not match a non pack in side a template – Oliv Feb 21 '18 at 11:33
  • @Oliv Upvoted for your explanation of gcc's behavior. However, if clang has any reason to reject this code, it should not be the "more specialized" part, otherwise the error message should be "class template partial specialization is not more specialized than the primary template". See demo [here](https://wandbox.org/permlink/yGcqZ4BEPoJt5qRV). In fact, clang does agree the "more specialization" part if tested with function. – llllllllll Feb 21 '18 at 11:37
  • @Oliv I'm wondering whether this kind of partial specialization is ill-formed because of other rules before checking "more specialized" part. – llllllllll Feb 21 '18 at 11:40
  • @liliscent I am no sure to understaind. For partial specialization template parameter order has no implication on the program semantic. `template struct foo` is the same as `template struct foo` Both clang and gcc complain on the demo. – Oliv Feb 21 '18 at 11:41
  • @Oliv All these 2 spec are wrong, but the error messages are different. – llllllllll Feb 21 '18 at 11:43
  • @liliscent I am doubting a lot, even about what I wrote. – Oliv Feb 21 '18 at 11:43
  • It does apply when checking (under the new rule) whether `template class P` as a template parameter is at least as specialized as `template class A` as an argument. It's not, so if you were to consider *just that part of the rules*, you couldn't pass A as a template argument when the corresponding parameter is P. However, there's still the rest of [temp.arg.template]/3, which makes A a valid argument in this case. – bogdan Feb 21 '18 at 11:49
  • @bogdan For the call `f_foo_partial(foo)` is not it the opposite: `templateclass P` and `template class A`. Then I don't see a match is the rest of [temp.arg.template]/3. – Oliv Feb 21 '18 at 12:04
  • Yes, that's correct: that call has been rendered valid by the new rule (it doesn't satisfy the condition in the rest of [temp.arg.template]/3, so it doesn't work in C++14). My previous comment refers to the other call, `f_foo(foo{})`, where the condition in the new rule is not satisfied because of [temp.deduct.type]/9.2, but the rest of [temp.arg.template]/3 makes it valid. – bogdan Feb 21 '18 at 12:24
  • 1
    [temp.arg.template]/3 only says if an argument **match** corresponding parameter, and the key is how to **deduce** the argument. – xskxzr Feb 21 '18 at 12:51
  • @bogdan So I edited my answer, and change it entirely to reflect your reasoning, which I believe is right. So the partial specialization is not more specialized. – Oliv Feb 21 '18 at 12:53
  • @xskxzr So the problem is to find an answer to your [question](https://stackoverflow.com/questions/48899131/is-template-namett-a-deduced-context?noredirect=1#comment84818588_48899131)? If the answer is that TTT is deduced context, will this answer be OK? – Oliv Feb 21 '18 at 13:19
  • 1
    I don't know. The standard seems not to specify how to deduce a template template argument. Hence I prefer `template-name` is non-deduced. – xskxzr Feb 21 '18 at 13:33
  • Upvoted. The section about template template-parameter matching could be improved. We're only interested in whether P is at least as specialized as A, not whether it's more specialized. So, we're only trying to deduce from P to A, but doing it twice, because this is "called" twice from the part that tries to deduce whether the partial specialization is more specialized than the primary template. Each time, P and A are different and the invented X has different template parameters, so I think this should be clarified. – bogdan Feb 21 '18 at 21:48
  • In summary, this was a valid partial specialization in C++14, but is no longer valid in C++17. I think the loss is minor compared to the benefits brought by the change, but it would be interesting to see some real code that would use such a partial specialization, to have an idea about what we've actually lost. – bogdan Feb 21 '18 at 21:53
1

It was valid in C++14. In C++17, the changes in P0522R0 made this invalid; however, there is an open core issue, CWG2398, regarding similar examples that were broken by the P0522R0 changes. A resolution to CWG2398 would likely make this example well-formed again. Continue reading for more detail.


Partial ordering rules in general

The basic idea behind partial ordering of class template specializations goes all the way back to C++98. To determine whether one class template partial specialization is more specialized than another (or more specialized than the primary template), we transform each partial specialization (or primary template) into a similar function template. See [temp.spec.partial.order]. From

template<template<typename ...> class>
struct foo {};     // 1

template<template<typename> class C>
struct foo<C> {};  // 2

we obtain the following function templates:

template<template<typename ...> class TT>
void fooF(foo<TT>);  // 1f

template<template<typename> class TT>
void fooF(foo<TT>);  // 2f

If (2f) is more specialized than (1f), then (2) is more specialized than (1).

(Note that the rule requiring partial specializations to be more specialized than the primary template was introduced in C++14. However, it was approved as a DR so it should be applied retroactively to previous versions as well.)

The general principle behind partial ordering of function templates also goes all the way back to C++98. The idea (as explained in [temp.func.order]) is that if we generate an "arbitrary" specialization of 2f, and 1f "accepts" this arbitrary specialization, then 1f is "at least as general" as 2f, or to put it another way, 2f is "at least as specialized" as 1f. We also need to check whether 1f is "at least as specialized" as 2f. In order to say that 2f is more specialized than 1f, it must be the case that 2f is at least as specialized as 1f, but 1f is not at least as specialized as 2f.

The way to generate such an "arbitrary" specialization of 2f, as described in [temp.func.order]/3, is to replace each template parameter that appears in 2f with a completely unique argument that's compatible with that template parameter; one that is unrelated to any other entity in the program. In 2f, since TT is a class template taking one type argument, we replace it with the following unique class template:

template <class> struct UniqueNV;

The transformed 2f is then:

void fooF(foo<UniqueNV>);  // 2t

We then have to check whether template arguments can be deduced for (1f) in order to make (1f) identical to (2t). In other words, is there TT such that void (foo<TT>) is the same as void(foo<UniqueNV>), where TT is a template <typename...> class? Call this (Q1).

When we compare in the opposite direction, i.e., ask whether 1f is at least as specialized as 2f, the same principle applies. 1f is transformed:

template <class...> struct UniqueV;
void fooF(foo<UniqueV>);  // 1t

and we try deduction against (2f): is there TT such that void (foo<TT>) is identical to void (foo<UniqueV>), considering that in this case TT is a template <typename> class, not a template <typename...> class? Call this (Q2).

In order for the OP's example to be valid, the answer to (Q1) must be "yes", and the answer to (Q2) must be "no".


The answers to the above questions changed in C++17 due to P0522R0. It was already previously the case that a template <typename...> class parameter could accept a template <typename> class argument, which meant the answer to (Q1) was always "yes"; TT is simply deduced to be UniqueNV. After P0522R0, it is also the case that a template <typename> class parameter can accept a template <typename...> class argument. This makes the answer to (Q2) also "yes", which means that (2) is no longer considered more specialized than (1).

The reason why GCC nevertheless accepts it is that GCC never implemented the resolution to CWG1495; it still uses the old C++11 rules, where the partial specialization simply can't be identical to the primary template. However, if we change the example so that GCC is forced to compare two different partial specializations, we see that in C++17 mode and above, GCC no longer considers the specialization that takes a unary class template to be more specialized than the one that takes a variadic class template:

template <int, template <typename...> class>
struct foo {};

template <template <typename...> class C>
struct foo<0, C> {};

template <template <typename> class C>
struct foo<0, C> {};

template <class C> struct S;

int main() {
    foo<0, S> f;
}

Godbolt link

Meanwhile, Clang has disabled the P0522 behaviour even in C++17 mode; you must turn it on using the -frelaxed-template-template-args option. Clang accepts the code when the P0522 behaviour is turned off, and rejects when it is turned on. Godbolt link


Common sense says that (2) should indeed be more specialized than (1), but the isn't currently a rule in the C++ standard that makes it so. The problem is that when P0522 changed the rules as to when a template argument is compatible with a template parameter, it did not also include an accompanying change to the partial ordering rules that would keep (2) more specialized than (1). In the issue description for CWG2398, you can see some other examples that are now broken. If CWG2398 is eventually fixed, then (2) will likely be considered more specialized than (1) again. (Well, at least as long as I'm in CWG, I'll try to make sure they don't forget to account for this particular case of it.)

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
0

Unfortunately not this way...

  • I want, in no point, try to get into the "Whys" the standard decided not to allow a partial specialization expressed this way – (or if specific wordings on it has been forgotten).
  • There are people here who are undoubtedly way more skilled than me at deciphering the convoluted subtleties of the wordings of the standard... ;)

That said, ...

  • If someone is looking for ways to implement a solution to this problem – (perfectly standard ways) – here are information and examples that will most likely help in solving your issue...


° Number of template parameters.

  • Before showing a means with which a specialization based on the number of parameters of a template template parameter can be achieve, I'm going to briefly talk about the template parameters in themselves.

(I might need to be corrected on the wordings, but the point should be clear enough)

The whole point with templates, when it comes to use them:

  • In using aliases.
  • With or without arguments.
  • Within dispatching traits.
  • Within detection traits.
  • Within specializations.

is about having the best possible understanding of when the types it receives have to be called parameters, and when to called them arguments...

It might seem obvious to you at first, but you might also be surprised to read that I have posted a very detailed solution, answering another question, to detect the number of parameters declared by a template.

The solution describes the searching process by first trying to 'count' the arguments passed to a template. In the end, because everything in the solution has been done only on forward declarations of templates which are never fully defined, I realized that it was a detection algorithm, because the results are obtained while none of the Victim templates gets ever instantiated...

  • Fully detailed explanations and implementation: here


° Template template parameter specialization.

(Back to the initial wondering)

I was trying to do the exact same type of specialization, shown in the very first code example of the question, to create some king of an implementation selector when I found this question...

I said to my self: "Ok, it seems that one cannot do it like that..."

Because of what I already did for the detection of the number of parameters declared by a template (referenced by the link above), I quickly found a means to produce the desired effect. I thought it would certainly be of use for others, and I've decided to post it here...

I think that the code is rather self-explanatory, so it shouldn't need more explanations.

// Helper to make the static_assert 'name-dependent'.
template<typename...> using AlwaysFalse = std::false_type;

// Primary template should only signal the error...
template<typename Trait>
struct ImplSelect {
    static_assert(
        AlwaysFalse<Trait>::value,
        "Missing specialization..."
    );
};

// Specialization for a template with one type parameter.
template<template <typename> class Trait, typename T>
struct ImplSelect<Trait<T>> { using Type = Trait<int>; };

// Specialization for a template with two type parameters.
template<template <typename, typename> class Trait, typename T, typename U>
struct ImplSelect<Trait<T, U>> { using Type = Trait<int, int>; };

/* [ Note ] These are ONLY forward declarations.
 *          They aren't instatiated within this code.
 */
template<typename T>             struct Victim1;
template<typename T, typename U> struct Victim2;

/* [ Note ] Opaque type tags (forward declarations).
 *          Allows for further tag dispatching if desired.
 */
struct Tag1; struct Tag2;

/* Selection of Victim1<int> and Victim2<int, int>.
 * Not instantiated yet...
 */
using Impl1 = typename ImplSelect<Victim1<Tag1>>::Type;
using Impl2 = typename ImplSelect<Victim2<Tag1, Tag2>>::Type;
Enjoy !
Tenphase
  • 101
  • 5