SFINAE based on the existence / absence of a class member

I have a basic understanding of SFINAE, for example. how enable_if works. I recently came across this answer , and I spent more than an hour trying to figure out how this really works to no avail.

The purpose of this code is to overload a function based on whether the class has a specific member in it. Here's the copied code, it uses C ++ 11:

 template <typename T> struct Model { vector<T> vertices; void transform( Matrix m ) { for(auto &&vertex : vertices) { vertex.pos = m * vertex.pos; modifyNormal(vertex, m, special_()); } } private: struct general_ {}; struct special_ : general_ {}; template<typename> struct int_ { typedef int type; }; template<typename Lhs, typename Rhs, typename int_<decltype(Lhs::normal)>::type = 0> void modifyNormal(Lhs &&lhs, Rhs &&rhs, special_) { lhs.normal = rhs * lhs.normal; } template<typename Lhs, typename Rhs> void modifyNormal(Lhs &&lhs, Rhs &&rhs, general_) { // do nothing } }; 

In my life I can’t wrap my head around how this mechanism works. In particular, what helps typename int_<decltype(Lhs::normal)>::type = 0 and why do we need an additional type ( special_ / general_ ) in this method.

+6
source share
3 answers

why do we need an extra type ( special_ / general_ ) in this method

They are used only so that the modifyNormal function is overloaded with various implementations. What is especially important in them is that special_ uses the IS-A relation, since it inherits from general_ . In addition, the transform function always calls modifyNormal overload, which is of type special_ , see the next part.

what helps us typename int_<decltype(Lhs::normal)>::type = 0

This is a template argument with a default value. A default value is present, so the transform function should not indicate it, which is important because this template parameter is not present in another modifyNormal function. In addition, this template parameter is added only for calling SFINAE.

http://en.cppreference.com/w/cpp/language/sfinae

When replacing the inferred type for the template parameter, the specialization is discarded from the overload set, and does not cause a compilation error.

So, if a crash occurs, the modifyNormal function using the special_ type is removed from the set of overloads for consideration. This causes the modifyNormal function to modifyNormal type general_ , and since special_ IS-A general_ type is still working.

If a replacement failure does not occur, then the modifyNormal function using the special_ type will be used because it matches better.


Note. The general_ type is a struct , so inheritance is public by default, which allows you to use IS-A relationships without using the public keyword.


Edit:

Can you comment on why we primarily use the complex typename int_<decltype(Lhs::normal)>::type mechanism typename int_<decltype(Lhs::normal)>::type ?

As stated above, this is used to trigger the behavior of SFINAE. However, it is not very difficult when you break it. In this case, he wants to create an instance of the int_ structure for some type T and has a data type :

 int_<T>::type 

Since this is used in the template, the typename keyword needs to be added, see When is the "typename" keyword required? .

 typename int_<T>::type 

Finally, what is the actual type used to instantiate the int_ structure? This is defined by decltype(Lhs::normal) , which tells the type for Lhs::normal . If the Lhs type has a normal data member, then everything will succeed. However, if this is not the case, the replacement will fail, and the importance of this is explained above.

+3
source

special_ / general_ are types used to make the compiler different between the two modifyNormal methods. Note that the third argument is not used. The general implementation does nothing, but in the particular case it changes the normal one. Also note that special_ is derived from general_ . This means that if the specialized version of modifyNormal not defined (SFINAE), the general case applies; if there is a specialized version, then it will be selected (this is more specific).

Now there is a switch in the definition of modifyNormal ; if the type (the first argument of the template) does not have a member with the name normal , then the template fails (SFINAE and does not complain about it, this is the SFINAE trick), and this will be another modifyNormal definition that will be applied (general case). If the type defines a member with the name normal , then the third argument of the template can be resolved to an additional third argument template (the default value of int is 0). This third argument has no function for the function, only for SFINAE (the template applies to it).

+1
source

The types general and special are used to force the compiler to select the first function (this is the best match) as the first attempt to resolve the call.
Please note that the call:

 modifyNormal(vertex, m, special_()); 

In any case, since general inherits from special , both are valid, and the second is selected if the first failure occurs when the type is replaced.

So, he wanted to say - let it go as if this method exists, but keep calm, because we have a catch-all callback that does nothing.

Why can this fail?
That int_ takes part in the game.
If decltype (let's say so) gives an error because the normal member method is omitted in Lhs , int_ cannot be specialized, and the replacement is not actually performed, but thanks to SFINAE, this failure is not an error as long as there is another substitution that you can try (and it exists in our case, a method that has general as an argument, which is still a less accurate match for the original call).

+1
source

All Articles