WTF is SFINAE?

2023-05-20

SFINAE is a Latin word derived from the expression homo faber suae quisque sfinae.

I’m just kidding: it’s an acronym that stands for the uninformative sentence Substitution Failure Is Not An Error. Let’s take a quick look at what this means.

SWASEINABT

(Starting With A Simple Example Is Not A Bad Thing)

Try to guess what the example below does.

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

using namespace std; // On small examples, your sins are forgiven

// alias for "return type of T[n]"
template<typename T>
using access_t = decltype(T::operator[](declval<typename T::value_type>()));

// use this if T[n] is a valid expression...
template<typename T, typename = access_t<T>>
typename T::value_type at(const T& container, size_t n) {
    return container[n];
}

// ...otherwise, use this
template<typename T>
typename T::value_type at(const T& container, size_t n) {
    size_t i = 0;
    for(auto it = container.begin(); it != container.end(); ++it) {
        if (i == n) return *it;
        ++i;
    }

    throw runtime_error("Out of bounds");
}

int main() {
    vector<int> a {1, 3, 5, 9, 1, 6};
    list<int> b {1, 4, 0, 8, 7};

    cout << "a[4] == " << at(a, 4)
         << "\nb[2] == " << at(b, 2) << endl;

    // Output:
    // a[4] == 1
    // b[2] == 0
}

This code snippet is defining the function at(container, n), which returns the n-th element of container. If the container supports indexing with the [] operator, it uses the first version of at. Otherwise, it uses the second version. In C++, vectors support [], but lists don’t, so we should see both functions being used here.

Let’s walk through the tricky bits:

In C++20 you no longer need the hack with access_t<T>: you can use concepts and contraints to the same effect.

template<typename T>
concept Indexable = requires(T t) {
    T::operator[];
};

template<Indexable T>
typename T::value_type at(const T& container, size_t n) {
    return container[n];
}

template<typename T>
typename T::value_type at(const T& container, size_t n) {
    size_t i = 0;
    for(auto it = container.begin(); it != container.end(); ++it) {
        if (i == n) return *it;
        ++i;
    }

    throw runtime_error("Out of bounds");
}

That’s the essence of it. Of course, there is a lot more to SFINAE, from void_t to enable_if, to this abstruse example from cppreference.com:

template<int I>
void div(char(*)[I % 2 == 0] = nullptr)
{
    // this overload is selected when I is even
}
 
template<int I>
void div(char(*)[I % 2 == 1] = nullptr)
{
    // this overload is selected when I is odd
}

In fact, I wanted to write a much longer article about SFINAE “from first principles”, but then realized that it was slowly turning into a full-blown primer on template metaprogramming that I didn’t have time to write. If you’re interested, here’s some further reading material.

Further reading