Code sample 1:
namespace detail {
enum enabler { dummy };
}
class foo {
public:
template <typename T,
typename std::enable_if<!std::is_integral<T>::value,
detail::enabler>::type = detail::enabler::dummy>
void func(T t) {
std::cout << "other" << std::endl;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value,
detail::enabler>::type = detail::enabler::dummy>
void func(T t) {
std::cout << "integral" << std::endl;
}
};
Code sample 2:
namespace detail {
enum enabler { dummy };
}
class foo {
public:
template <typename T,
typename T2 = typename std::enable_if<!std::is_integral<T>::value, detail::enabler>::type>
void func(T t) {
std::cout << "other" << std::endl;
}
template <typename T,
typename T2 = typename std::enable_if<std::is_integral<T>::value, detail::enabler>::type>
void func(T t) {
std::cout << "integral" << std::endl;
}
};
I understand why sample 2 does not compile. Basically because both template functions are like each other (they even have the same template parameters T
, T2
).
I do not understand why sample 1 compiles! I see that it's the same problem. Basically instead of having the second template parameter as a typename
, it's an enum
instead of a typename
this time.
Can anybody explain please?
Further, I executed the following piece of code and it returned true which is even more confusing! (makes sense that it's true but confusing that sample 1 compiles)
std::cout
<< std::boolalpha
<< std::is_same<std::enable_if<std::is_integral<int>::value, int>::type,
std::enable_if<!std::is_integral<float>::value, int>::type>::value
<< std::endl;
Default arguments (whether default function arguments or default template arguments) are not part of the function signature. You are only allowed to define one function with a given signature.
In code sample 1, if we drop the names of the arguments and all the defaults, we have two functions that are:
template <typename T, std::enable_if_t<!std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }
template <typename T, std::enable_if_t<std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }
Those are two different signatures - the 2nd template parameter has different types in both functions - so it's valid from this perspective.
Now, what happens when we actually call it. If T
is an integral type, the 2nd argument gets substituted in both cases:
template <typename T, ????> void func(T ) { ... }
template <typename T, detail::enabler = dummy> void func(T ) { ... }
In the first function, the expression typename std::enable_if<false, detail::enabler>::type
is ill-formed. There is no type
typedef on that type. Typically, writing ill-formed code is a hard compiler error. But, thanks to a rule called SFINAE (Substitution Failure Is Not An Error), ill-formed code arising in the immediate context of template substitution is not an error - it just causes that function/class template specialization to be removed from the consideration set. So we end up with just one valid one:
template <typename T, detail::enabler = dummy> void func(T ) { ... }
which gets called.
However, in code sample 2, we have these two functions:
template <typename T, typename>
void func(T ) { ... }
template <typename T, typename>
void func(T ) { ... }
Those are the same! We're defining the same function twice - which is not allowed, hence the error. This is an error for the same reason that:
int foo(int x = 1) { return x; }
int foo(int x = 2) { return x; }
is an error.
This is why Xeo used the (unconstructible) enum for enable if to begin with - it makes it easier to write disjoint function template specializations since you can do it with the enum but you can't do it with types.
Note that you could similarly just do the same with something like int
:
template <class T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
void func(T ) { ... }
But this would allow an evil user to provide a value for that argument (it wouldn't matter in this particular context but it could in others). With detail::enabler
, no such value can be provided.
I guess I came to a conclusion (maybe anybody could verify or update):
In sample 2:
When the compiler encounters a function call and tries to match it to a template it finds two templates with parameters typename T
and typename T2
(the default values does not matter, this is normal) and this creates an ambiguity.
In sample 1:
When the compiler enounters a function call it will try to match it with one of the two templates, one of them will succeed so it will have typename T
and detail::enabler
and the other will have no value defined for the second template parameter so the ambiguity will be removed in this case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With