Sign in to follow this  

[C++] Could someone please explain "SFINAE"

This topic is 3722 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

The parashift C++ faq seemed to have the most gentle explanation of "SFINAE", but I still don't get it. Could someone give me a "For dummies" explanation of what "SFINAE" is? Also how does "SFINAE" translate into a powerful/useful language feature?

Share this post


Link to post
Share on other sites

#include <iostream>
#include <typeinfo>

template<typename T> void foo(T* x)
{ std::cout << "foo<" << typeid(T).name() << ">(T*)\n"; }

void foo(int x)
{ std::cout << "foo(int)\n"; }

void foo(double x)
{ std::cout << "foo(double)\n"; }

int main()
{
foo(42); // matches foo(int) exactly
foo(42.0); // matches foo(double) exactly
foo("abcdef"); // matches foo<T>(T*) with T = char
return 0;
}

Shouldn't that be:

foo<char>("abcdef");
?

Share this post


Link to post
Share on other sites
I believe the C++-faq explanation is somewhat difficult to understand.

What is SFINAE? It's the difference between errors in the function signature and errors in the function body, for template functions. Errors in the function signature are ignored, while errors in the function body are signaled. So, if your template function, for a parameter T, uses an argument type or return type which fails to compile or doesn't exist, then the error is ignored and the function is removed from the potentially usable functions for a given call. Conversely, once your function has been selected for the call, the template is instantiated and any errors in the body manifest themselves.

Why SFINAE? Consider the following example:

// Suppose C is a class template which doesn't work for 'int'
template <typename T> C;

template <typename T>
void foo(T t, C<T> c);

void foo(int a, int b);

int main()
{
foo(10,20);
}


When looking for the correct overload of foo, the compiler attempts to instantiate the template function, and fails (the second argument is invalid). Without SFINAE, the compiler would then provoke an error because of a function which you didn't even expect to come into account there, before actually trying to determine which overload to use.

SFINAE allows you to effectively ignore template functions which don't work.

What can one do with SFINAE? Well, boost::enable_if and boost::disable_if are good examples. Instead of relying on an internal compiler error in the function body to disable the function (e.g. when not having a default constructor and using a vector's resize ability, causing a puzzling error message), you simply blast the function out of existence by making it depend on a compile-time condition.

In short: you generate a compiler error as part of the function's return type or argument types whenever the template parameter doesn't satisfy your criteria (for instance, by having a nested type such as is_valid<T>::yes which only exists when T is indeed valid (and is a typedef for void). This will make the function disappear from the compiler radar whenever it would be used with an incorrect type, simplifying the error diagnosis (you get a notification about "X doesn't exist" instead of "deep inside Z inside Y inside X, you have used something that isn't allowed") and also allowing a diagnosis when the method isn't fully implemented yet (or reserved for future constraints).

Quote:
Original post by Alpha_ProgDes
Shouldn't that be:

foo<char>("abcdef");
?


It can, but it doesn't have to. The template argument can be unambiguously deduced as being char.

Share this post


Link to post
Share on other sites
So if I understand correctly:

The compiler needs to decide which overload to use. The compiler cannot choose a correct template function based on signature alone, so it will continue to compile the body of the template function. If an error occurs during the this process the failed template function is simply removed from the list of overload candidates. The compiler then tries the next overload candidate.

So to reiterate: substitution failure of a template function is not an error.

Quote:
Original post by ToohrVyk
In short: you generate a compiler error as part of the function's return type or argument types whenever the template parameter doesn't satisfy your criteria (for instance, by having a nested type such as is_valid<T>::yes which only exists when T is indeed valid (and is a typedef for void). This will make the function disappear from the compiler radar whenever it would be used with an incorrect type, simplifying the error diagnosis (you get a notification about "X doesn't exist" instead of "deep inside Z inside Y inside X, you have used something that isn't allowed") and also allowing a diagnosis when the method isn't fully implemented yet (or reserved for future constraints).


I understand that usage on a high level, but I don't know how it would look in code.

How would you implement is_valid<T>::yes ?

Share this post


Link to post
Share on other sites
Quote:
Original post by fpsgamer
The compiler needs to decide which overload to use. The compiler cannot choose a correct template function based on signature alone, so it will continue to compile the body of the template function. If an error occurs during the this process the failed template function is simply removed from the list of overload candidates. The compiler then tries the next overload candidate.


The compiler doesn't look at the body of the function. It simply tries to replace the type parameter with something that would result in a correct function signature. If, while replacing that type parameter, it encounters an error (because this would lead to instantiating a class template that's used as an argument or return type, and this class template instantiation is incorrect) as part of the signature, it doesn't report an error.

Quote:
How would you implement is_valid<T>::yes ?


For instance, suppose you want your 'is valid' to represent vectors of pointers and lists of pointers:

#include <list>
#include <iostream>
#include <vector>

template<typename T>
struct is_valid {};

template<typename T, typename A>
struct is_valid< std::vector<T*,A> >
{
typedef void yes;
};

template<typename T, typename A>
struct is_valid< std::list<T*,A> >
{
typedef void yes;
};

template<typename T>
typename is_valid<T>::yes operate(T & t)
{
typename T::iterator it = t.begin();
while (it != t.end())
{
delete *it;
++it;
}
t.clear();
}

int main()
{
std::vector<int> a;
std::vector<int*> b;

operate(b); // Ok!
operate(a); // no matching function
}



Share this post


Link to post
Share on other sites

This topic is 3722 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this