Question

I have the following C++11 code.

#include <type_traits>

using IntType = unsigned long long;

template <IntType N> struct Int {};

template <class T>
struct is_int : std::false_type {};

template <long long N>
struct is_int<Int<N>> : std::true_type {};

int main()
{
    static_assert (is_int<Int<0>>::value, "");
    return 0;
}

Clang++ 3.3 compiles the code but on g++ 4.8.2 static assertion fails

$ g++ -std=c++11 main.cpp 
main.cpp: In function ‘int main()’:
main.cpp:15:5: error: static assertion failed: 
     static_assert (is_int<Int<0>>::value, "");
     ^
$ 

The problem is caused by different integral template parameters. Which compiler is right in this case?

Was it helpful?

Solution

The surprise

This is a subtle Clang bug, deeply buried in the Standard. The problem is that in almost all cases, non-type template arguments can be converted to the type of the template parameter. E.g. the expression Int<0> has an int literal argument of value 0 that is being converted to the type unsigned long long of the template parameter N.

14.8.2 Template argument deduction [temp.deduct]/2 2nd bullet

-- Non-type arguments must match the types of the corresponding non-type template parameters, or must be convertible to the types of the corresponding non-type parameters as specified in 14.3.2, otherwise type deduction fails.

Since your class template is_int<T> has a partial specialization, we need to look at

14.5.5.1 Matching of class template partial specializations [temp.class.spec.match]

1 When a class template is used in a context that requires an instantiation of the class, it is necessary to determine whether the instantiation is to be generated using the primary template or one of the partial specializations. This is done by matching the template arguments of the class template specialization with the template argument lists of the partial specializations.

2 A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list (14.8.2).

So it would seem that we can proceed to the earlier quote of 14.8.2/2 2nd bullet and match the second specialization (although in that case an even more complicated overload resolution game would have to be played).

The resolution

However, it turns out (as mentioned by @DyP in the comments) that another clause in the Standard supersedes this:

14.8.2.5 Deducing template arguments from a type [temp.deduct.type]

17 If, in the declaration of a function template with a non-type template-parameter, the non-type templateparameter is used in an expression in the function parameter-list and, if the corresponding template-argument is deduced, the template-argument type shall match the type of the template-parameter exactly, except that a template-argument deduced from an array bound may be of any integral type. [ Example:

template<int i> class A { / ... / };
template<short s> void f(A<s>);
  void k1() {
  A<1> a;
  f(a); // error: deduction fails for conversion from int to short
  f<1>(a); // OK
}

The upshot is that the partial specialization of is_int cannot be deduced because it does not take the exact same type (unsigned long long vs long long) as the Int class template's formal non-type template parameter.

You can resolve this by giving the non-type template parameter N in the partial specialization of is_int the same type as the non-type parameter N in the primary template Int.

template <IntType N>
//        ^^^^^^^^         
struct is_int<Int<N>> : std::true_type {};

Live Example.

OTHER TIPS

Clang is being inconsistent. Since it accepts your code, I'm expecting the following code must output f(Int<long long>) instead of f(T):

using IntType = unsigned long long;
template <IntType N> struct Int {};

template<typename T>
void f(T) { std::cout << "f(T)" << std::endl; }

template<long long N>
void f(Int<N>) { std::cout << "f(Int<long long>)" << std::endl; }

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

But surprisingly, it outputs this (online demo):

f(T)

That shows Int<0> does NOT match with the second overload which accepts the argument as Int<N>. If that is so, then why does it match with Int<N> when it is used as template argument to the class template (in your case)?

My conclusion:

  • If Clang is correct in my case, then it is incorrect in your case.
  • If Clang is correct in your case, then it is incorrrect in my case.

Either way, Clang seems to have bug.

GCC, on the other hand, is consistent at least. That doesn't prove though that it doesn't have bug — it might mean that it has bug in both cases! Unless someone comes up with the standardese and showing it has bug too, I'm going to trust GCC in this case.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top