Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should the following code compile according to C++ standard?

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

like image 851
Predelnik Avatar asked Dec 14 '16 13:12

Predelnik


Video Answer


2 Answers

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.

like image 195
Columbo Avatar answered Oct 24 '22 23:10

Columbo


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

like image 35
Maciej Załucki Avatar answered Oct 24 '22 23:10

Maciej Załucki