17
#include <type_traits>

template <typename T>
struct C;

template<typename T1, typename T2>
using first = T1;

template <typename T>
struct C<first<T, std::enable_if_t<std::is_same<T, int>::value>>>
{
};

int main ()
{
}

Results of compilation by different compilers:

MSVC:

error C2753: 'C': partial specialization cannot match argument list for primary template

gcc-4.9:

error: partial specialization 'C' does not specialize any template arguments

clang all versions:

error: class template partial specialization does not specialize any template argument; to define the primary template, remove the template argument list

gcc-5+: successfully compiles

And additionaly I want to point out that trivial specialization like:

template<typename T>
struct C<T>
{
};

successfully fails to be compiled by gcc. So it seems like it figures out that specialization in my original example is non-trivial. So my question is - is pattern like this explicitly forbidden by C++ standard or not?

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
Predelnik
  • 5,066
  • 2
  • 24
  • 36
  • 7
    I'm starting to wish I never wrote [this thing](http://stackoverflow.com/a/31213703/2756719). The question is [whether the attempted partial specialization is more specialized than the primary template](https://timsong-cpp.github.io/cppwp/temp.class.spec#8.2), which is determined by comparing them [after rewriting to two function templates](https://timsong-cpp.github.io/cppwp/temp.class.order). Unfortunately, [it's not even clear whether after the rewrite the two function template declarations declare the same function template or not](https://wg21.link/cwg1980). – T.C. Dec 14 '16 at 13:29
  • 2
    @T.C. I was in the course of writing that deduction entails substitution of arguments into non-deduced occurrences in `P` (so the second template is more specialized because the unique synthesized type will fail to trigger `is_same`'s partial specialization), but I'm not sure that's explicit in the wording anywhere, nor to what extent it actually applies. – Columbo Dec 14 '16 at 13:32
  • @Columbo The end of [temp.deduct.type]/1 ("that will make P, after substitution of the deduced values (call it the deduced A)"), sort of? But I think it's unclear at best how to handle this substitution in any event. What if the trait is `template struct is_not_int : true_type {}; template<> struct is_not_int : false_type {};`? A strict reading would say that the partial specialization is then invalid, but that seems...bizarre at best. – T.C. Dec 14 '16 at 13:38
  • @Columbo That part is basically https://wg21.link/cwg1157, with an extra twist due to alias templates. – T.C. Dec 14 '16 at 13:45

2 Answers2

5

The crucial paragraph is [temp.class.spec]/(8.2), which requires the partial specialization to be more specialized than the primary template. What Clang actually complains about is the argument list being identical to the primary template's: this has been removed from [temp.class.spec]/(8.3) by issue 2033 (which stated that the requirement was redundant) fairly recently, so hasn't been implemented in Clang yet. However, it apparently has been implemented in GCC, given that it accepts your snippet; it even compiles the following, perhaps for the same reason it compiles your code (it also only works from version 5 onwards):

template <typename T>
void f( C<T> ) {}

template <typename T>
void f( C<first<T, std::enable_if_t<std::is_same<T, int>::value>>> ) {}

I.e. it acknowledges that the declarations are distinct, so must have implemented some resolution of issue 1980. It does not find that the second overload is more specialized (see the Wandbox link), however, which is inconsistent, because it should've diagnosed your code according to the aforementioned constraint in (8.2).

Arguably, the current wording makes your example's partial ordering work as desired: [temp.deduct.type]/1 mentions that in deduction from types,

Template arguments can be deduced in several different contexts, but in each case a type that is specified in terms of template parameters (call it P) is compared with an actual type (call it A), and an attempt is made to find template argument values […] that will make P, after substitution of the deduced values (call it the deduced A), compatible with A.

Now via [temp.alias]/3, this would mean that during the partial ordering step in which the partial specialization's function template is the parameter template, the substitution into is_same yields false (since common library implementations just use a partial specialization that must fail), and enable_if fails. But this semantics is not satisfying in the general case, because we could construct a condition that generally succeeds, so a unique synthesized type meets it, and deduction succeeds both ways.

Presumably, the simplest and most robust solution is to ignore discarded arguments during partial ordering (making your example ill-formed). One can also orientate oneself towards implementations' behaviors in this case (analogous to issue 1157):

template <typename...> struct C {};

template <typename T>
void f( C<T, int> ) = delete;

template <typename T>
void f( C<T, std::enable_if_t<sizeof(T) == sizeof(int), int>> ) {}

int main() {f<int>({});}

Both Clang and GCC diagnose this as calling the deleted function, i.e. agree that the first overload is more specialized than the other. The critical property of #2 seems to be that the second template argument is dependent yet T appears solely in non-deduced contexts (if we change int to T in #1, nothing changes). So we could use the existence of discarded (and dependent?) template arguments as tie-breakers: this way we don't have to reason about the nature of synthesized values, which is the status quo, and also get reasonable behavior in your case, which would be well-formed.


@T.C. mentioned that the templates generated through [temp.class.order] would currently be interpreted as one multiply declared entity—again, see issue 1980. That's not directly relevant to the standardese in this case, because the wording never mentions that these function templates are declared, let alone in the same program; it just specifies them and then falls back to the procedure for function templates.

It isn't entirely clear with what depth implementations are required to perform this analysis. Issue 1157 demonstrates what level of detail is required to "correctly" determine whether a template's domain is a proper subset of the other's. It's neither practical nor reasonable to implement partial ordering to be this sophisticated. However, the footnoted section just goes to show that this topic isn't necessarily underspecified, but defective.

Columbo
  • 60,038
  • 8
  • 155
  • 203
1

I think you could simplify your code - this has nothing to do with type_traits. You'll get the same results with following one:

template <typename T>
struct C;

template<typename T>
using first = T;

template <typename T>
struct C<first<T>>  // OK only in 5.1
{
};

int main ()
{
}

Check in online compiler (compiles under 5.1 but not with 5.2 or 4.9 so it's probably a bug) - https://godbolt.org/g/iVCbdm

I think that int GCC 5 they moved around template functionality and it's even possible to create two specializations of the same type. It will compile until you try to use it.

template <typename T>
struct C;

template<typename T1, typename T2>
using first = T1;

template<typename T1, typename T2>
using second = T2;

template <typename T>
struct C<first<T, T>>  // OK on 5.1+
{
};

template <typename T>
struct C<second<T, T>>  // OK on 5.1+
{
};

int main ()
{
   C<first<int, int>> dummy; // error: ambiguous template instantiation for 'struct C<int>'
}

https://godbolt.org/g/6oNGDP

It might be somehow related to added support for C++14 variable templates. https://isocpp.org/files/papers/N3651.pdf

Maciej Załucki
  • 464
  • 1
  • 4
  • 15
  • It seems like in 5.1 your example works but starting with 5.2 it fails to compile even with one trivial template alias with "error: partial specialization 'struct C' does not specialize any template arguments". So gcc has been evolving in that regard it seems :) – Predelnik Dec 15 '16 at 08:22
  • Most probably it's bug introduced with __N3651__ to __GCC 5.1__ and partialy fixed in __5.2__. Partially because second example will compile on __5.2 up to 6.2__ (didn't test further). Note that I edited second example by adding second, dummy template type. I'm not sure if it's really a bug or a feature. In my understanding it will specialize __struct C__ for __int__ twice. In case of function it would be problem as you can specialize function in separate compilation unit and link with it - you'd get multiple definition. In case of class/struct it's a bit more complicated. – Maciej Załucki Dec 15 '16 at 21:01
  • Anyways, if compiler lets you define multiple specializations for template class, they how we can decide which one to one? We can't. – Maciej Załucki Dec 15 '16 at 21:02
  • FYI, I reported it to GCC to either get clarification or fix a bug. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78825 – Maciej Załucki Dec 15 '16 at 22:00