17
votes

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 ? )

2
If such specialization is valid then instantiation of foo<bar> with template<typename...> struct bar {}; is ambiguous, isn't it?Nikita Kniazev
@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. It unconditionally uses the specialized version...llllllllll
About your your ecample foo<bar> 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
@Walter clang 5.0 and clang 7.0 HEAD. The live demo is in question.llllllllll
@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

2 Answers

2
votes

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.

0
votes

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 !