C++: SFINAE

Say you have this piece of code:

template <typename T>
void show(typename T::iterator x, typename T::iterator y)
{
	for (; x != y; ++x) cout << *x << endl;
}

int main()
{
	show<int>(16, 18);
}

If you compile the code you will get an error similar to this one:


vec.cpp: In function 'int main()':
vec.cpp:22:18: error: no matching function for call to 'show(int, int)'
vec.cpp:22:18: note: candidate is:
vec.cpp:7:57: note: template void show(typename T::iterator, typename T::iterator)

And the error is quite obvious: your T type (in this case, int) does not declare an inner type called iterator, so the function substitution is unable to get compiled.

But consider if you add this extra template function to the program after the implemnetation of the first show function:

template <typename T>
void show(T a, T b)
{
	cout << a << "; " << b << endl;
}

If you recompile your code, you will see it compiles perfectly and runs perfectly.

That is SFINAE. SFINAE stands for “Substitution Failure Is Not An Error” and is a C++ compiler feature that works in this way: When a substitution failure is found by the compiler (for example, when the compiler tried to substitute T::iterator to int::iterator), instead of raise an error and terminate abruptly; the compiler just ignores this substitution and searches for other valid one. If no valid substitution is found, then an error is raised, otherwise, the proper substitution is being used.

And what is this useful for?

A lot of interesting validations and some sort of type introspection can be done using SFINAE. For example, following my example above, I would want to have a way of knowing if a data type has an iterator or not. So using SFINAE I could define a struct like this one:

template <typename T>
struct has_iterator
{	
	template <typename U>
	static char test(typename U::iterator* x);

	template <typename U>
	static long test(U* x);

	static const bool value = sizeof(test<T>(0)) == 1;
};

How does it work?

Let’s invoke it using a type with no iterator: int:


cout << has_iterator<int>::value << endl;

In this case, the compiler creates a has_iterator and evaluates value to: value = sizeof(test<int>(0)) == 1;. Then the compiler tries with the first substitution and it is unable to compile int::iterator; so, it ignores such method and goes to the next one that contains a valid implementation. The method should return a “long” that has a size always different to 1 (please see the Marcin’s comment about this in the comments section). So the evaluation for value returns false.

If we invoke it using a type with iterator: vector:


cout << has_iterator<vector<int>>::value << endl;

The first substitution is valid so the compiler will use it. The function returns a “char” and the size of a char is always 1 so the evaluation returns true.

Notice that I did not implement the test methods; I just declare them and that is the only thing needed by the compiler to generate the valid code for all cases.

Nice, isn’t it?

Advertisements

7 thoughts on “C++: SFINAE

  1. Thanks for your clear illustration on this topic! I finally understand what it means now.

  2. Nice explaination, very practical motivation and clear example – I like it!
    Still, “long has always size different to 1” is only true in practice. sizeof(char) is guaranteed to be 1, and sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long). One might argue that long is no smaller than int, and int is guaranteed to be large enough to contain INT_MAX and INT_MIN, which, as far as I remamber, cannot fit in 8 bits in 2's complement. Nevertheless, while 'sizeof' returns size in "bytes", "byte" is not necessarily 8 bits in the Standard – only the lower bound on its size is given :)

    Bulletproof solution would be to return reference to an array of more than 1 chars:
    static char (&test(U*))[2];
    or struct containing it. Using struct makes the example longer, returning an array reference – less readable, imo. Meh… maybe just add "almost" somewhere in that sentence :P

  3. Hello, I am quite new to the whole type_traits concept and so far this is the best explanation I have seen about this vague metaprogramming subject. The only thing I am having a problem understanding is the syntax of the test(0) – and how this is expanded. It seems like for the case when an ‘int’ is substituted for the template parameter being passed in, i.e. test(0) then the 0 corresponds to x in ‘static long test(U* x)’ substitution. So this means 0 is a valid pointer which is valid? When I tried to use test(1) instead, the compiler returned:

    error: no matching function for call to ‘has_iterator<std::vector<std::basic_string > >::test(int)’
    static const bool value = sizeof(test(1)) == 1;

    The same goes for substitution when the substituted type does contain an iterator (or it looks like an iterator pointer from the code), the Syntax ‘typename U::iterator* x’ with the pointer and the explicit typename is a little confusing to a newbie like myself, can you give a little more explanation so I can untangle the substitution steps in my head. Great article!

    John

    1. John:
      The 0 must be a pointer, not an integer. 1 has problems because 1 is not a valid pointer. I think you can use three well know pointers: 0, nullptr (almost identical to 0 in c++11) and void. I didn’t try void but I think should work.
      Mario.

  4. Good post. I agree with Marcin’s comments. There are compilers for cpu or dsp architectures where each address accesses a “word” that is 32 or even 64 bits wide, and sizeof(byte) == 1, sizeof(int) == 1 and sizeof(long) == 1. This is why the only certainty of sizeof(char) is that it is not greater than the size of any other integral type.

  5. The best explanation I have read about SFINAE… same applies to your ‘enable_if’ post too. Thanks a lot oopscene, this was really helpful for me… :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s