What is SFINAE in C++?
Can you please explain it in words understandable to a programmer who is not versed in C++? Also, what concept in a language like Python does SFINAE correspond to?
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.
So the simple answer is YES.
" 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.
Defining a Function TemplateA function template starts with the keyword template followed by template parameter(s) inside <> which is followed by the function definition. In the above code, T is a template argument that accepts different data types ( int , float , etc.), and typename is a keyword.
Warning: this is a really long explanation, but hopefully it really explains not only what SFINAE does, but gives some idea of when and why you might use it.
Okay, to explain this we probably need to back up and explain templates a bit. As we all know, Python uses what's commonly referred to as duck typing -- for example, when you invoke a function, you can pass an object X to that function as long as X provides all the operations used by the function.
In C++, a normal (non-template) function requires that you specify the type of a parameter. If you defined a function like:
int plus1(int x) { return x + 1; }
You can only apply that function to an int
. The fact that it uses x
in a way that could just as well apply to other types like long
or float
makes no difference -- it only applies to an int
anyway.
To get something closer to Python's duck typing, you can create a template instead:
template <class T> T plus1(T x) { return x + 1; }
Now our plus1
is a lot more like it would be in Python -- in particular, we can invoke it equally well to an object x
of any type for which x + 1
is defined.
Now, consider, for example, that we want to write some objects out to a stream. Unfortunately, some of those objects get written to a stream using stream << object
, but others use object.write(stream);
instead. We want to be able to handle either one without the user having to specify which. Now, template specialization allows us to write the specialized template, so if it was one type that used the object.write(stream)
syntax, we could do something like:
template <class T> std::ostream &write_object(T object, std::ostream &os) { return os << object; } template <> std::ostream &write_object(special_object object, std::ostream &os) { return object.write(os); }
That's fine for one type, and if we wanted to badly enough we could add more specializations for all the types that don't support stream << object
-- but as soon as (for example) the user adds a new type that doesn't support stream << object
, things break again.
What we want is a way to use the first specialization for any object that supports stream << object;
, but the second for anything else (though we might sometime want to add a third for objects that use x.print(stream);
instead).
We can use SFINAE to make that determination. To do that, we typically rely on a couple of other oddball details of C++. One is to use the sizeof
operator. sizeof
determines the size of a type or an expression, but it does so entirely at compile time by looking at the types involved, without evaluating the expression itself. For example, if I have something like:
int func() { return -1; }
I can use sizeof(func())
. In this case, func()
returns an int
, so sizeof(func())
is equivalent to sizeof(int)
.
The second interesting item that's frequently used is the fact that the size of an array must to be positive, not zero.
Now, putting those together, we can do something like this:
// stolen, more or less intact from: // http://stackoverflow.com/questions/2127693/sfinae-sizeof-detect-if-expression-compiles template<class T> T& ref(); template<class T> T val(); template<class T> struct has_inserter { template<class U> static char test(char(*)[sizeof(ref<std::ostream>() << val<U>())]); template<class U> static long test(...); enum { value = 1 == sizeof test<T>(0) }; typedef boost::integral_constant<bool, value> type; };
Here we have two overloads of test
. The second of these takes a variable argument list (the ...
) which means it can match any type -- but it's also the last choice the compiler will make in selecting an overload, so it'll only match if the first one does not. The other overload of test
is a bit more interesting: it defines a function that takes one parameter: an array of pointers to functions that return char
, where the size of the array is (in essence) sizeof(stream << object)
. If stream << object
isn't a valid expression, the sizeof
will yield 0, which means we've created an array of size zero, which isn't allowed. This is where the SFINAE itself comes into the picture. Attempting to substitute the type that doesn't support operator<<
for U
would fail, because it would produce a zero-sized array. But, that's not an error -- it just means that function is eliminated from the overload set. Therefore, the other function is the only one that can be used in such a case.
That then gets used in the enum
expression below -- it looks at the return value from the selected overload of test
and checks whether it's equal to 1 (if it is, it means the function returning char
was selected, but otherwise, the function returning long
was selected).
The result is that has_inserter<type>::value
will be l
if we could use some_ostream << object;
would compile, and 0
if it wouldn't. We can then use that value to control template specialization to pick the right way to write out the value for a particular type.
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