Как проверить, имеет ли шаблонный класс член-функцию?
Можно ли написать шаблон, который изменяет свое поведение в зависимости от того, определена ли определённая функция-член в классе?
Вот простой пример того, что я хотел бы реализовать:
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 ответ(ов)
Да, с помощью 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. Не знаю, будет ли это портируемо на других платформах с другими компиляторами.
Хотя этот вопрос был задан два года назад, я решусь добавить свой ответ. Надеюсь, он прояснит предыдущие, безусловно, отличные решения. Я объединил очень полезные ответы Никола Бонелли и Иоганнеса Шаута в решение, которое, на мой взгляд, более читаемо, понятно и не требует расширения 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. Основной вклад принадлежит Никола Бонелли и Иоганнесу Шаубу, поэтому дайте им положительный голос, если мой ответ вам помог 😃
Вот простой вариант решения для 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
, и возвращать соответствующее значение в зависимости от результата проверки.
Это именно для чего предназначены типовые трейт. К сожалению, их нужно определять вручную. В вашем случае можно представить следующее:
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
.
Вот самый лаконичный способ, который я нашел в 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
Где и зачем нужно использовать ключевые слова "template" и "typename"?
В чем разница между параметрами шаблона "typename" и "class"?
Хранение определений шаблонных функций C++ в .CPP файле
QT: Шаблонизированный класс с Q_OBJECT
Что такое Правило трёх?