1
votes

This looks like an issue in clang (I've already opened a bug here), but I'd like to be sure that I'm not doing a mistake.

Consider the following code:

#include <type_traits>
#include <cstddef>

template<std::size_t N, std::size_t M, std::enable_if_t<not (N>M)>* = nullptr> // (1)
struct S: public S<N+1, M> { };

template<std::size_t N>
struct S<N, N> { };

int main() {
    S<0, 1> c{};
}

It fails to compile with the following error:

8 : error: non-type template argument specializes a template parameter with dependent type 'std::enable_if_t M)> *' (aka 'typename enable_if M), void>::type *')
struct S { };

The same code works as expected using the following line instead of (1):

template<std::size_t N, std::size_t M, typename = std::enable_if_t<not (N>M)>>

The SFINAE expression is almost the same. It is based on a specialization of std::enable_if_t and I would expect the same result (success or failure) for both of the examples.
Are my expectations wrong?

Note that GCC works fine in either cases.

1
The whole thing seems to be non-working to me. The parameter type in the primary template is always going to be constructed, regardless of whether it or the specialization is selected. So the enable_if will always fail and the main method will be ill-formed.Johannes Schaub - litb
The relevant text is "In a type name that refers to a class template specialization, (e.g., A<int, int, 1>) the argument list shall match the template parameter list of the primary template. The template arguments of a specialization are deduced from the arguments of the primary template.". The "arguments of the primary template" is the implicit argument list that conceptually follows the unspecialized case: struct S<N,M,<unnamed>>. And in your case, the unnamed argument got an invalid type, which will result in ill-formed code.Johannes Schaub - litb
@JohannesSchaub-litb The whole thing works as expected with GCC (even though it shouldn't, but the sfinae expression is valid). It still works if you change it in typename = std::enable_if<..., this time both with GCC and clang. As an example, S<0, 2> is accepted, while S<2, 0> is not. The enable_if won't fail in the first case, but it will in the second. I don't get your point, I'm sorry.skypjack
my point is that your parameter type is not an SFINAE context. You have probably seen enable_if expressions in partial specializations, where they are in SFINAE contexts, but as a type in a primary template they are not SFINAE contexts. And placing it as a default argument probably does something else than what you think it does: You possibly expected this to compile: coliru.stacked-crooked.com/a/486ba2517bf67c18Johannes Schaub - litb
what I'm saying is that it's not an sfinae expression. It's just a "substitution failure is an error" expression. If N>M in S<N,M>, your code is always ill-formed. There is no "not an error" part of SFINAE in there. The term SFINAE is used for cases where substitution fails, but where it is not an error and an alternative declaration can be considered (S<2, 1> in my example).Johannes Schaub - litb

1 Answers

2
votes

I think this is a gcc bug actually, as a result of [temp.class.spec]:

The type of a template parameter corresponding to a specialized non-type argument shall not be dependent on a parameter of the specialization. [ Example:

template <class T, T t> struct C {};
template <class T> struct C<T, 1>;       // error

template< int X, int (*array_ptr)[X] > class A {};
int array[5];
template< int X > class A<X,&array> { }; // error

—end example ]

In your example, the type of the 3rd template parameter is dependent on a parameter. When you swap it to typename = std::enable_if_t<...>, then this rule no longer applies.


Note: is there any reason to use SFINAE here anyway, as opposed to static_assert-ing?