I am currently trying to understand a C++ code, and have come across SFINAE construct (which is new to me). I have created a minimal example, based on the code I am looking at below:
#include<iostream>
/* ----------------------------------------------
Define two kernels: characterized by their dimension
---------------------------------------------- */
struct Kern2
{
static constexpr int dim = 2;
};
struct Kern3
{
static constexpr int dim = 3;
};
/* ----------------------------------------------
Choose which function to evaluate based on
dimension of Kern (Kern::dim)
---------------------------------------------- */
template<class Kern,
typename std::enable_if<Kern::dim == 2, bool>::type = true>
inline void apply_kern(){
std::cout << "dim=2" << "\n";
}
template<class Kern,
typename std::enable_if<Kern::dim == 3, bool>::type = false>
inline void apply_kern(){
std::cout << "dim=3" << "\n";
}
// Try to see if the above SFINAE construct works!
int main()
{
apply_kern<Kern2>(); // should print 'dim=2'
apply_kern<Kern3>(); // should print 'dim=3'
return 0;
}
This gives as output:
> dim=2
> dim=3
which is exactly what it's supposed to do. However, I am unable to understand exactly how this works? In particular, it appears that the same output is created if I switch the
typename std::enable_if<Kern::dim == 2, bool>::type = true
lines to:
typename std::enable_if<Kern::dim == 2, bool>::type = false
So I'm wondering what the meaning of these is? If someone could kindly explain what's going on, I'd greatly appreciate it! I haven't been able to find this precise way to use SFINAE online, unfortunately.
Thanks!
Substitution failure is not an error (SFINAE) refers to a situation in C++ where an invalid substitution of template parameters is not in itself an error. David Vandevoorde first introduced the acronym SFINAE to describe related programming techniques.
" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.
typename std::enable_if<Kern::dim == 2, bool>::type = true>
That says:
typename:
the following term defines a type
std::enable_if<Kern::dim == 2, bool>
This template defines a type of the second template parameter IF the condition in the first parameter is true. So here, if dimm == 2 is true, the template std::enable_if
provide a type bool which can be accessed with the ::type
.
If the condition was true, the term:
typename std::enable_if<Kern::dim == 3, bool>::type
becomes simply:
bool
Now you add = true
after it. Did you use the bool value anywhere? NO! So it simply doesn't matter at all! you also can write:
typename std::enable_if<Kern::dim == 3, int>::type = 42
It will result in the same, as you did not use the value you define here!
The condition you check is in Kern::dim == 3
. This one must be true or false.
If the condition is evaluated to false
, the template enable_if
did not contain a type
and the expression fails. Here SFINAE comes into play. This failure will not be an error but makes the template definition "invisible" as it "can not" be used cause of the failure.
Add-On for the addition question in the comments:
Sure, you can add a name to your bool template default paramter and use it in your code below like this:
template<class Kern,
typename std::enable_if<Kern::dim == 2, bool>::type myVal = true>
inline void apply_kern(){
std::cout << "dim=2" << "\n";
std::cout << "bool val: " << myVal << std::endl;
}
BTW: We often see SFINAE used in cases, where a simple template overload works the same way. Often the overload is easier to read ( here maybe not :-) ). I give it only as a hint: Check if SFINAE is really needed and think of a overload instead.
Template overload instead of SFINAE:
/* ----------------------------------------------
Define two kernels: characterized by their dimension
---------------------------------------------- */
struct Kern2 { static constexpr int dim = 2; };
struct Kern3 { static constexpr int dim = 3; };
/* ----------------------------------------------
Choose which function to evaluate based on
dimension of Kern (Kern::dim)
---------------------------------------------- */
template < int x > inline void apply_kern_impl();
template<>
inline void apply_kern_impl<2>() { std::cout << "dim=2" << "\n"; }
template<>
inline void apply_kern_impl<3>() { std::cout << "dim=3" << "\n"; }
template< typename T>
inline void apply_kern() { apply_kern_impl<T::dim>(); }
int main()
{
apply_kern<Kern2>(); // should print 'dim=2'
apply_kern<Kern3>(); // should print 'dim=3'
return 0;
}
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