c++ – How to check if class T has foo method?

Question:

Is it possible in C++ to write something like this:

template<class T>
void f(T a)  {
  if ( Существет метод a.foo ) {
    a.foo();
  } else {
    myfoo(a);
  }
}

Those. the function should behave differently depending on whether class T has method foo or not.

Answer:

This is done like this:

#include <iostream>
struct A{
    void foo(){}
};

struct B{};

template<class T>
struct Test{
    typedef void(T::*P)(void);

    template<class U, P = &U::foo>
    struct True{char dummy[2];};

    typedef char False;

    static False detect(...);

    template<class U>
    static True<U> detect(U*);

    static const bool exists = (sizeof(False) != sizeof(detect(static_cast<T*>(0))));
};


int main(){
    std::cout << std::boolalpha << Test<B>::exists << std::endl; //false
    std::cout << std::boolalpha << Test<A>::exists << std::endl; //true
}

Now about what is going on here. The code uses an idiom called SFINAE. The abbreviation SFINAE stands for substitution failure is not an error and means the following: when defining function overloads, erroneous template instantiations do not cause a compilation error, but are discarded from the list of candidates for the most appropriate overload .

Now let's see how this code will behave. The Test class defines two types True and False . Their important property is that sizeof(False) != sizeof(True<T>) .

We also have two detect methods at our disposal. The first takes a pointer to T , the second takes an arbitrary number of arguments (an ellipsis). Ellipsis is omnivorous, and at the same time has the lowest priority when choosing an overload .
When detect(static_cast<T*>(0) is called, the compiler tries to find an appropriate overload. The template version of detect has higher priority. But if the type T does not have a void foo() method, then the template instantiation will fail. But as we remember, substitution failure is not an error The compiler will give preference to the ellipsis.

We can find out which version of detect was chosen by the type of the return value. And we can distinguish types by their size.

Note that there is no implementation of the detect methods in the code. It uses the fact that sizeof does not evaluate the value of the expression, it immediately extracts the type . Thus the detect methods will never be called.

As a result, the variable exists will be true if the type T has a method void foo() .

All code is executed at compile time and does not provide any overhead.

Also, this code was compiled by the online compiler in C++98 compatibility mode

UPD: I was hinted in the comments that this does not solve your problem. To some extent, this is true. The resulting value cannot simply be used in an if , as this will result in a compilation error. We need an analogue of if , which would work at the compilation stage. The role of such an if can be template specialization:

template<class T, bool b = Test<T>::exists>
struct Foo;

template<class T>
struct Foo<T, true>{
    static void foo(const T &t){
        t.foo();
    }
};

template<class T>
struct Foo<T, false>{
    static void foo(const T &t){
        std::cout << "foo" << std::endl;
    }
};

The Foo structure has two specializations. The first one is for the case Test<T>::exists == true , the second one is for false . Well, a small cherry on the cake for convenience, adding nothing new except automatic inference of the type of arguments:

template<class T>
void foo(const T &t){
    Foo<T>::foo(t);
}

Now main will look like this:

int main(){
    A a;
    B b;

    foo(a); //A::foo
    foo(b); //foo
}

Complete example

Scroll to Top