Checks for specific member functions in a class may actually be used in the workplace. You can do this with the SFINAE technique in C++.

SFINAE stands for Substitution Failure Is Not An Error. It is an advanced skill in C++ template programming, but a basic skill in template metaprogramming. I’m not really an expert in C++ metaprogramming, but I’ve collected some common implementations and done some testing. In the process, I found some common ways of writing SFINAE to be problematic, which I will explore below.

For example, let’s check to see if there is a push_back() member in the C++ library class. Before C++11, it was possible to write this with no problem when tested:

#include <iostream>
#include <map>
#include <list>
#include <set>
#include <string>
#include <vector>
struct has_push_back {

    template <typename C, void (C::*)(const typename C::value_type&)>
    struct Helper;

    template <typename C, void (C::*)(typename C::value_type)>
    struct Helper2;

    template <typename C>
    static bool test(...). {
        return false;
    }
    template <typename C>
    static bool test(Helper<C, &C::push_back>*) {
        return true;
    }
    template <typename C>
    static bool test(Helper2<C, &C::push_back>*) {
        return true; }};int main(a) {
    std::cout << has_push_back::test<std::list<int> > (NULL) << std::endl;
    std::cout << has_push_back::test<std::map<int.int> > (NULL) << std::endl;
    std::cout << has_push_back::test<std::set<int> > (NULL) << std::endl;
    std::cout << has_push_back::test<std::string>(NULL) << std::endl;
    std::cout << has_push_back::test<std::vector<int> > (NULL) << std::endl;
    return 0;
}
Copy the code

SFINAE can be implemented in many ways, and the details may vary.

In the template parameters of both Helper classes. The second argument is the type of function pointer to push_back. We created two helpers because STD ::string push_back is char. That is, value_type. Other STL containers. Const value_type&. That’s why we use two helpers. If you’re checking for other member functions, such as size, you don’t need to bother with just one Helper.

The test function, for template functions that return true, takes a pointer. So when you actually check, you pass in a NULL to match.

C + + 11 after:

#include <iostream>
#include <list>
#include <map>
#include <set>
#include <string>
#include <vector>

template <typename>
using void_t = void;

template <typename T, typename V = void>
struct has_push_back:std::false_type {};

template <typename T>
struct has_push_back<T, void_t<decltype(std::declval<T>().push_back(std::declval<typename T::value_type>()))>>:std::true_type {};

int main(a) {
    std::cout << has_push_back<std::list<int>>::value << std::endl;
    std::cout << has_push_back<std::map<int.int>>::value << std::endl;
    std::cout << has_push_back<std::set<int>>::value << std::endl;
    std::cout << has_push_back<std::string>::value << std::endl;
    std::cout << has_push_back<std::vector<int>>::value << std::endl;
    return 0;
}
Copy the code

C++11 is much cleaner. If the requirement is to detect any member function, without specifying which one, macros are definitely needed. Change the above code to the version of the macro, using push_back as an argument to the macro.

Why am I using push_back() here? Many of the various implementations of SFINAE available online have problems with push_back detection. Push_back (); push_back(); push_back(); In versions prior to C++11, you need to be able to enumerate the types of arguments to push_back, which can be cumbersome if there are many overloaded versions of member functions to check. So it’s still C++11 after C++11 that is concise and universal.

Here is a common but sometimes problematic SFINAE sample:

class Base {

};
class Drive:Base {
public:
    void hello(a) {}};template <typename T>
struct has_hello {
    typedef char Yes[1]; // or typedef int8_t Yes;
    typedef char No[2];  // or typedef int16_t No;

    template <typename>
    static No& has(...).;

    template <typename C>
    static Yes& has(decltype(&C::hello));

    static const bool value = sizeof(has<T>(NULL)) = =sizeof(Yes);
};

int main(a) {
    std::cout << has_hello<Base>::value << std::endl;
    std::cout << has_hello<Drive>::value << std::endl;
}
Copy the code

OK, this is used to check whether the class has a hello member function is OK. But changing to push_back is problematic.

#include <iostream>
#include <list>
#include <map>
#include <set>
#include <string>
#include <vector>
// This is not the case
template <typename T>
struct has_push_back {
    typedef char Yes[1];
    typedef char No[2];

    template <typename>
    static No& has(...).;

    template <typename C>
    static Yes& has(decltype(&C::push_back));

    static const bool value = sizeof(has<T>(NULL)) = =sizeof(Yes);
};

int main(a) {
    std::cout << has_push_back<std::list<int> >::value << std::endl;
    std::cout << has_push_back<std::map<int.int> >::value << std::endl;
    std::cout << has_push_back<std::set<int> >::value << std::endl;
    std::cout << has_push_back<std::string>::value << std::endl;
    std::cout << has_push_back<std::vector<int> >::value << std::endl;
    return 0;
}
Copy the code

Push_back (string); push_back (string); push_back (string); push_back (string); Vector and list are both checked incorrectly.

There are many other variants of this version as well. The so-called variation mainly changes the return value of HAS and the judgment of value. Also have certain problem, specific everybody tests.