6

Как проверить, имеет ли шаблонный класс член-функцию?

28

Можно ли написать шаблон, который изменяет свое поведение в зависимости от того, определена ли определённая функция-член в классе?

Вот простой пример того, что я хотел бы реализовать:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

Таким образом, если в классе T определена функция toString(), то она будет использоваться; в противном случае - нет. Магическая часть, которую я не знаю, как реализовать, - это часть "FUNCTION_EXISTS". Как это можно сделать?

5 ответ(ов)

3

Да, с помощью SFINAE можно проверить, предоставляет ли данный класс определённый метод. Вот работающий код:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// Тест SFINAE
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test(decltype(&C::helloworld) );
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
    
int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

Я протестировал это с Linux и компиляторами gcc 4.1/4.3. Не знаю, будет ли это портируемо на других платформах с другими компиляторами.

0

Хотя этот вопрос был задан два года назад, я решусь добавить свой ответ. Надеюсь, он прояснит предыдущие, безусловно, отличные решения. Я объединил очень полезные ответы Никола Бонелли и Иоганнеса Шаута в решение, которое, на мой взгляд, более читаемо, понятно и не требует расширения typeof:

template <class Type>
class TypeHasToString
{
    // Этот тип не будет компилироваться, если второй параметр шаблона не является типом T,
    // поэтому я могу поместить тип указателя на функцию в первый параметр, а саму функцию
    // во второй, тем самым проверяя, что функция имеет конкретную сигнатуру.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // Вспомогательная структура для хранения объявления указателя на функцию.
    // Измените ее, если сигнатура функции изменится.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

Я проверял это с gcc 4.1.2. Основной вклад принадлежит Никола Бонелли и Иоганнесу Шаубу, поэтому дайте им положительный голос, если мой ответ вам помог 😃

0

Вот простой вариант решения для C++11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> std::string
{
    return "toString not defined";
}

Обновление, через 3 года: (и это непроверенный код). Для проверки существования метода toString, я думаю, это будет работать:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

Таким образом, с помощью этих шаблонов можно легко проверять, имеет ли объект метод toString, и возвращать соответствующее значение в зависимости от результата проверки.

0

Это именно для чего предназначены типовые трейт. К сожалению, их нужно определять вручную. В вашем случае можно представить следующее:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
};

Таким образом, если у вас есть тип, который имеет функцию toString, вы просто создаете специальизацию шаблона для этого типа, где устанавливаете значение has_tostring в true.

0

Вот самый лаконичный способ, который я нашел в C++20 и который очень близок к вашему вопросу:

template<class T>
std::string optionalToString(T* obj)
{
  if constexpr (requires { obj->toString(); })
    return obj->toString();
  else
    return "toString не определен";
}

Посмотреть в действии можно на godbolt: https://gcc.godbolt.org/z/5jb1d93Ms

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь