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?
Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.
Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.
Lost your password? Please enter your email address. You will receive a link and will create a new password via email.
Please briefly explain why you feel this question should be reported.
Please briefly explain why you feel this answer should be reported.
Please briefly explain why you feel this user should be reported.
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:
You can only apply that function to an
int. The fact that it usesxin a way that could just as well apply to other types likelongorfloatmakes no difference — it only applies to anintanyway.To get something closer to Python’s duck typing, you can create a template instead:
Now our
plus1is a lot more like it would be in Python — in particular, we can invoke it equally well to an objectxof any type for whichx + 1is 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 useobject.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 theobject.write(stream)syntax, we could do something like: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 supportstream << 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 usex.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
sizeofoperator.sizeofdetermines 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:I can use
sizeof(func()). In this case,func()returns anint, sosizeof(func())is equivalent tosizeof(int).The second interesting item that’s frequently used is the fact that the size of an array must be positive, not zero.
Now, putting those together, we can do something like this:
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 oftestis a bit more interesting: it defines a function that takes one parameter: an array of pointers to functions that returnchar, where the size of the array is (in essence)sizeof(stream << object). Ifstream << objectisn’t a valid expression, thesizeofwill 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 supportoperator<<forUwould 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
enumexpression below — it looks at the return value from the selected overload oftestand checks whether it’s equal to 1 (if it is, it means the function returningcharwas selected, but otherwise, the function returninglongwas selected).The result is that
has_inserter<type>::valuewill be1ifsome_ostream << object;would compile, and0if 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.