GrabDuck

Как проверить есть ли у класса Т метод foo?

:

Это делается так:

#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
}

Теперь о том, что же здесь творится. В коде используется идиома под названием SFINAE. Аббревиатура SFINAE расшифровывается как substitution failure is not an error и означает следующее: при определении перегрузок функции ошибочные инстанциации шаблонов не вызывают ошибку компиляции, а отбрасываются из списка кандидатов на наиболее подходящую перегрузку.

Теперь посмотрим как будет себя вести этот код. В классе Test определено два типа True и False. Их важным свойством является то, что sizeof(False) != sizeof(True<T>).

Так же в нашем распоряжении есть два метода detect. Первый принимает указатель на T, второй принимает произвольное количество аргументов(эллипсис). Эллипсис является всеядным, и в тоже время обладает самым низким приоритетом при выборе перегрузки.
При вызове detect(static_cast<T*>(0) компилятор пробует подобрать подходящую перегрузку. Более высоким приоритетом обладает шаблонная версия detect. Но если у типа T не окажется метода void foo(), то инстанцирование шаблона провалится. Но как мы помним substitution failure is not an error. Компилятор отдаст предпочтение эллипсису(тореточие).

Узнать какая версия detect была выбрана мы можем по типу возвращаемого значения. А отличить типы мы можем по их размеру.

Обратите внимание, в коде нет реализации методов detect. Тут используется тот факт что sizeof не вычисляет значение выражения, сразу извлекает тип. Таким образом методы detect никогда не будут вызваны.

В итоге в переменной exists окажется true если у типа T есть метод void foo().

Весь код выполняется на этапе компиляции, и не дает никаких накладных расходов.

Так же этот код скомпилировался онлайн-компилятором в режиме совместимости с C++98

UPD: В комментариях мне намекнули, что это не решает вашей проблемы. В какой-то мере это так. Полученное значение нельзя просто использовать в if-е, так как это приведет к ошибке компиляции. Нам нужен аналог if, который бы отрабатывал на этапе компиляции. В роли такого if-а может выступить специализация шаблонов:

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;
    }
};

Структура Foo имеет две специализации. Первая для случая Test<T>::exists == true, вторая для false. Ну и небольшая вишенка на торте для удобства, не добавляющая ничего нового кроме автоматического вывода типа аргументов:

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

Теперь main примет такой вид:

int main(){
    A a;
    B b;

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

Полный пример