Basic Problem Statement
I'm learning about SFINAE. I tried an extremely simple enable_if
:
// 1: A foo() that accepts arguments that are derived from Base
template <typename T, typename Enable = enable_if_t<std::is_base_of_v<Base, T>>>
void foo(T thing) {
std::cout << "It's a derived!" << std::endl;
}
// 2: A foo() that accepts all other arguments
template <typename T, typename Enable = enable_if_t<!std::is_base_of_v<Base, T>>>
void foo(T thing) {
std::cout << "It's not a derived." << std::endl;
}
The compiler complains that foo
is multiply defined. The internet tells me that this is because the template arguments aren't relevant when checking function signatures.
Variants I Tried
In my quest to do the most basic metaprogramming possible, I began throwing syntax at the problem. Here's a list of things that I tried for the enable_if
statement (inverted statement i.e. !std::is_base_of
identical, but omitted for brevity):
Anonymous Type, No typename
, Equals 0
https://en.cppreference.com/w/cpp/types/enable_if tells me that what I did above was wrong. But its suggestion (found under the first notes block) is appropriately cryptic, and more importantly, also doesn't compile.
std::enable_if_t<std::is_base_of_v<Base, T>> = 0
Anonymous Type, No typename
, Equals void
Thinking that maybe if I'm programming with types, using a type would be a wise choice, I instead tried to default the template to void
. No dice.
std::enable_if_t<std::is_base_of_v<Base, T>> = void
Anonymous Type, Yes typename
, Equals void
While we're throwing syntax at it, if I'm defaulting this template parameter to a type, shouldn't I use the typename keyword?
typename std::enable_if_t<std::is_base_of_v<Base, T>> = void
What Finally And Oh So Obviously Worked
typename enable_if_t<std::is_base_of_v<Base, T>, T>* = nullptr
I've asked everyone I know why this works yet my other variants don't, and they are equally confused. I'm at a loss. To make matters more confusing, if I name this type (e.g. typename Enable = ...
), it fails to compile.
I would be extremely grateful if one who is more familiar with TMP and enable_if
would explain to me:
enable_if
as a pointer to a type and defaulting it to nullptr
work?enable_if
?enable_if
?Many thanks.
enable_if is an extremely useful tool. There are hundreds of references to it in the C++11 standard template library. It's so useful because it's a key part in using type traits, a way to restrict templates to types that have certain properties. Without enable_if, templates are a rather blunt "catch-all" tool.
Introduction. The enable_if family of templates is a set of tools to allow a function template or a class template specialization to include or exclude itself from a set of matching functions or specializations based on properties of its template arguments.
The first set of variants you are just setting the value of a template type argument. Two overloads with different values for a template type argument collide, as they are both of kind template<class,class>
and have the same function arguments.
The non-type template argument cases, the ones where you use a raw enable if you end up having a template non type argument of type void. That is illegal; the various error messages are the various ways it is illegal.
When you add a star, when the enable if clause passes it is a template non type argument of type void pointer.
When it fails, it isn't an argument at all.
An equivalent to the nullptr case is:
std::enable_if_t<std::is_base_of_v<Base, T>, bool> = true
when the clause is true, the enable if evaluates to bool
, and we get:
bool = true
a template non-type argument of type bool that defaults to true. When the clause (the base of clause) is false, we get a SFINAE failure; there is no template type or non-type argument there.
With the class Whatever = enable_if
cases we are trying SFINAE based on default value of template arguments. This leads to signature collision, because signatures have to be unique if they are found during overload resolution (in the same phase).
With the enable = value
cases, we are trying SFINAE based on if there is a template non-type argument there. On failure, there is no signature to compare, so it cannot collide.
What remains is to make the syntax simple and pretty.
Now, this is all obsolete with Concepts, so don't fall in love with the syntax.
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