cplusplus.co.il

Substitution failure is not an error, part II

Posted on: 12/09/2009

As promised, this article continues and provides a thorough explanation of the previous post.

Let’s look at the code constructing the HasX<T> class, line by line:

template<typename T>
struct HasX {

This is the definition of a templated class, HasX<T>.

    struct Fallback {
        int x;
    }; // introduce member name “x”

This code defines an inner class, Fallback, with one member – named x. Using proper terminology, this class is essentially a Mixin.

    struct Derived : T, Fallback { };

Here we introduce a new class: Derived. It inherits both from the class T we’re templated on, and the previous Fallback class. Note that the Derived class inherits the x member from Fallback and possibly another x from T. Keep this (possible) ambiguity in mind.

    template<typename C, C>
    struct ChT;

The ChT class is templated on a typename C, and an object of that type. As a side note, only compile time constant fixed types are possible here. We will use this class to generate the ambiguity mentioned above.

    template<typename C>
    static char (&f(ChT<int Fallback::*, &C::x>*))[1];

This is where the fun starts. These two lines form a declaration of a templated function f. That function receives a pointer to the ChT class, instantiated with a pointer-to-member type whose parameter is the address-of-member x in class C. Note that if we attempt to instantiate this with the Derived class, one of two things will happen: either we will have a substitution failure due to the aforementioned ambiguity (if T also has a member by that name), or it will be successful (if there is no member called x, in T). This function returns a reference to a char array of size 1 (reference to array is discussed here).

    template<typename C>
    static char (&f(...))[2];

This is what gets instantiated if the previous template can’t be instantiated and SFINAE kicks in (since variadic functions have the lowest possible priority when selecting which overloaded function to call). This variadic function returns a reference to a char array of size 2. Note that instantiation of this function means that the previous one failed, implying that class T has a member named x.

    static bool const value = sizeof(f<Derived>(0)) == 2;
};

As you could guess, we will try to check which f function can be instantiated. We will do this by checking the sizeof of the return value of that f. If the first signature did not fit (could not be instantiated), then according to SFINAE the second one will be the chosen one (since it can’t fail) and we will take the sizeof of a char array of size 2, which is 2. Therefore, the value will be evaluated to true – meaning that class T really has a member by the name x. Otherwise, the first function can be successfully instantiated, and we will measure the sizeof of a char array of size 1, which is 1. In that case the value is evaluated to false – which is good for us, since that means there was no ambiguity when using Derived::x, and that means there was no other x but the one in Fallback.

Voila, I hope that everything is clear now. The techniques mentioned in this article are very elegant (at least in my eyes) and can be transformed and utilized to handle various similar cases.

2 Responses to "Substitution failure is not an error, part II"

Hey, two thumbs up!

Kudos for posting such a useful weblog. Your weblog isn’t only informative and also very artistic too. There usually are very couple of people who can write not so easy articles that creatively. Keep up the good writing !!

Leave a comment

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 27 other subscribers

Categories