Reputation: 35
I have been working with templates for quite some time now but recently I encountered a weird template programming for SFINAE.
My question is why do we write
typename = {something}
as a template parameter in C++?
Upvotes: 0
Views: 3026
Reputation: 25603
That is simply how SFINAE works ;)
As you know, you have to "create a failure" within the template declaration and not inside the template definition. As this:
template < typename X, typename = ... here is the code which may generate an error during instantiation >
void Bla() {}
The only chance to put some "code" in the declaration is to define something in the template parameter list or inside the template function declaration itself like:
template < typename X>
void Bla( ... something which may generates an error ) {}
Example:
template <typename T, typename = std::enable_if_t< std::is_same_v< int, T>>>
void Bla()
{
std::cout << "with int" << std::endl;
}
template <typename T, typename = std::enable_if_t< !std::is_same_v< int, T>>>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
}
int main()
{
Bla<int>();
Bla<std::string>();
}
But what is the background of "creating an substitution failure" here?
The trick is somewhere behind std::enable_if
. We can also use it without that:
template <typename T, typename = char[ std::is_same_v< int, T>]>
void Bla()
{
std::cout << "with int" << std::endl;
}
template <typename T, typename = char[ !std::is_same_v< int, T>]>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
}
Take a look on: typename = char[ !std::is_same_v< int, T>]
Here std::is_same_v
gives us a bool value back which is casted to 0 if not valid and to any postive number if valid. And creating a char[0]
is simply an error in c++! So with the negation of our expression with !
we got one for "is int" and one for "is not int". Once it tries to create an array of size 0, which is a failure and the template will not be instantiated and once it generates the type char[!0]
which is a valid expression.
And as the last step:
... typename = ...
is meant as: there will be defined a type, here without a template parameter name, as the parameter itself is not used later. You also can write:
... typename X = ...
but as X is not used, leave it!
Summary: You have to provide some code, which generates an error or not, depending on the type or value of a given expression. The expression must be part of the function declaration or part of the template parameter list. It is not allowed to put an error inside the function/class definition, as this will not longer be "not an error" in the sense of SFINAE.
Update: Can the results of SFINAE expressions be used for further expressions: Yes
Example:
template < typename TYPE >
void CheckForType()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template <typename T, typename X = std::enable_if_t< std::is_same_v< int, T>, float>>
void Bla()
{
std::cout << "with int" << std::endl;
CheckForType<X>();
}
template <typename T, typename X = std::enable_if_t< !std::is_same_v< int, T>, double >>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
CheckForType<X>();
}
int main()
{
Bla<int>();
Bla<std::string>();
}
Output:
with int
void CheckForType() [with TYPE = float]
with something else
void CheckForType() [with TYPE = double]
Explanation:
std::enable_if
has a second template parameter which is used as return type, if the first parameter of it will be true
. As this, you can use that type here. As you can see, the function CheckForType
is called with this defined type for X
from the SFINAE expression.
Upvotes: 2
Reputation: 119867
This construct is a nameless template parameter supplied with a default argument. Normally a template parameter would look like
typename <identifier> [ = <type> ]
but the identifier may be omitted.
You can see this construction often in SFINAE templates because it's a convenient way to enable a specialisation if and only if some condition holds, or if some piece of code is valid. Thus one can often see
template <typename T, typename = std::enable_if<(some-constexpr-involving-T)>::type> ...
If the expression happens to evaluate to true, all is well: std::enable_if<true>::type
is just void
, and we have a good working template specialisation.
Now if the expression above happens to be false, std::enable_if<false>
doesn't have a member named type
, Substitution Failure (the SF in SFINAE) happens, and the template specialisation is not considered.
And here the role of tge parameter ends. It is not used for anything in the template itself, so it doesn't need a name.
Upvotes: 1
Reputation: 36473
An example :
//for enum types.
template <typename T, typename std::enable_if<std::is_enum_v<T>, int>::type = 0>
void Foo(T value)
{
//stuff..
}
I only want this Foo
to be picked if T
is an enum type. We can achieve this using the rule of Substitute Failure Is Not An Error (SFINAE).
This rule states that if there's a failure during substitution this shouldn't be a compilation error. The compiler should only eliminate the function for this substitution.
So then, how do we make sure Foo
is only called with an enum type? Well that's easy, we find a way to trigger a substitution failure!
If we check cppreference for std::enable_if
it says:
template< bool B, class T = void >
struct enable_if;
If
B
istrue
,std::enable_if
has a public member typedeftype
, equal toT
; otherwise, there is no member typedef.
This means, if std::is_enum<T>::value
is true
(which is the case for enum types for T
) then B
will be true
and thus type
will be a valid type, an int
as specified.
If however std::is_enum::value
is false
then B
will be false
and thus type
won't even exist. In this case the code is trying to use ::type
while it doesn't exist and thus that's a substitution error. So SFINAE kicks in and eliminates it from the candidate list.
The reason we use = 0
is to provide a default template parameter value since we're not actually interested in using this type or its value, we only use it to trigger SFINAE.
Upvotes: 1