Можно ли написать шаблон для проверки существования функции?
Можно ли написать шаблон, который меняет поведение в зависимости от того, определена ли определенная функция-член в классе?
Вот простой пример того, что я хотел бы написать:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
Так что если class T
имеет toString()
определяется, затем он использует его; в противном случае это не так. Волшебная часть, которую я не знаю, как сделать, это часть "FUNCTION_EXISTS".
34 ответа
Да, с SFINAE вы можете проверить, предоставляет ли данный класс определенный метод. Вот рабочий код:
#include <iostream>
struct Hello
{
int helloworld() { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
typedef long two;
template <typename C> static one test( typeof(&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. Я не знаю, переносимо ли это на другие платформы, на которых работают другие компиляторы.
Этот вопрос старый, но в C++11 мы получили новый способ проверки существования функций (или существования любого нетипичного члена, на самом деле), снова полагаясь на SFINAE:
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
-> decltype(os << obj, void())
{
os << obj;
}
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
-> decltype(obj.stream(os), void())
{
obj.stream(os);
}
template<class T>
auto serialize(std::ostream& os, T const& obj)
-> decltype(serialize_imp(os, obj, 0), void())
{
serialize_imp(os, obj, 0);
}
Теперь о некоторых объяснениях. Во-первых, я использую выражение SFINAE, чтобы исключить serialize(_imp)
функции от разрешения перегрузки, если первое выражение внутри decltype
не действителен (иначе функция не существует).
void()
используется, чтобы сделать тип возврата всех этих функций void
,
0
аргумент используется, чтобы предпочесть os << obj
перегрузка, если оба доступны (буквальный 0
имеет тип int
и как таковая первая перегрузка лучше подходит).
Теперь вы, вероятно, хотите, чтобы признак проверял, существует ли функция. К счастью, это легко написать. Тем не менее, обратите внимание, что вам нужно написать характеристику для каждого имени функции, которое вам может понадобиться.
#include <type_traits>
template<class>
struct sfinae_true : std::true_type{};
namespace detail{
template<class T, class A0>
static auto test_stream(int)
-> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
template<class, class A0>
static auto test_stream(long) -> std::false_type;
} // detail::
template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};
И по объяснениям. Первый, sfinae_true
является вспомогательным типом, и это в основном равнозначно написанию decltype(void(std::declval<T>().stream(a0)), std::true_type{})
, Преимущество просто в том, что он короче.
Далее struct has_stream : decltype(...)
наследует от любого std::true_type
или же std::false_type
в конце концов, в зависимости от того, decltype
регистрироваться test_stream
не удается или нет.
Прошлой, std::declval
дает вам "значение" любого типа, который вы передаете, без необходимости знать, как вы можете его построить. Обратите внимание, что это возможно только в недооцененном контексте, таком как decltype
, sizeof
и другие.
Обратите внимание, что decltype
не обязательно нужно, так как sizeof
(и все неоцененные контексты) получили это улучшение. Это просто decltype
уже поставляет тип и как таковой просто чище. Вот sizeof
Версия одной из перегрузок:
template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
int(*)[sizeof((os << obj),0)] = 0)
{
os << obj;
}
int
а также long
параметры все еще там по той же причине. Указатель массива используется для предоставления контекста, в котором sizeof
может быть использован.
Набор инструментов для обнаружения
N4502 предлагает использовать детектор для включения в стандартную библиотеку C++17, которая может решить проблему несколько элегантным способом. Более того, он только что был принят в основы библиотеки TS v2. Он вводит некоторые метафункции, в том числе std::is_detected
который может быть использован для простой записи метафункций обнаружения типов или функций поверх них. Вот как вы можете использовать это:
template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );
template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;
Обратите внимание, что приведенный выше пример не проверен. Набор инструментов обнаружения пока недоступен в стандартных библиотеках, но предложение содержит полную реализацию, которую вы можете легко скопировать, если она вам действительно понадобится. Это хорошо играет с функцией C++17 if constexpr
:
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>)
return obj->toString();
else
return "toString not defined";
}
Boost.TTI
Еще один идиоматический инструментарий для выполнения такой проверки - хотя и менее элегантный - это Boost.TTI, представленный в Boost 1.54.0. Для вашего примера вы должны использовать макрос BOOST_TTI_HAS_MEMBER_FUNCTION
, Вот как вы можете использовать это:
#include <boost/tti/has_member_function.hpp>
// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)
// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;
Затем вы можете использовать bool
создать проверку SFINAE.
объяснение
Макрос BOOST_TTI_HAS_MEMBER_FUNCTION
генерирует метафункцию has_member_function_toString
который принимает проверенный тип в качестве первого параметра шаблона. Второй параметр шаблона соответствует типу возврата функции-члена, а следующие параметры соответствуют типам параметров функции. Член value
содержит true
если класс T
имеет функцию-член std::string toString()
,
С другой стороны, has_member_function_toString
может принимать указатель на функцию-член в качестве параметра шаблона. Следовательно, можно заменить has_member_function_toString<T, std::string>::value
от has_member_function_toString<std::string T::* ()>::value
,
C++ позволяет использовать SFINAE для этого (обратите внимание, что с функциями C++11 это проще, потому что он поддерживает расширенный SFINAE для почти произвольных выражений - ниже было создано для работы с обычными компиляторами C++03):
#define HAS_MEM_FUNC(func, name) \
template<typename T, typename Sign> \
struct name { \
typedef char yes[1]; \
typedef char no [2]; \
template <typename U, U> struct type_check; \
template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
template <typename > static no &chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
вышеупомянутый шаблон и макрос пытается создать экземпляр шаблона, давая ему тип указателя на функцию-член и фактический указатель на функцию-член. Если типы не подходят, SFINAE вызывает игнорирование шаблона. Использование как это:
HAS_MEM_FUNC(toString, has_to_string);
template<typename T> void
doSomething() {
if(has_to_string<T, std::string(T::*)()>::value) {
...
} else {
...
}
}
Но обратите внимание, что вы не можете просто назвать это toString
функция в том, если филиал. так как компилятор проверит правильность в обеих ветвях, это может привести к сбою в случаях, когда функция не существует. Одним из способов является использование SFINAE еще раз (enable_if также можно получить из boost):
template<bool C, typename T = void>
struct enable_if {
typedef T type;
};
template<typename T>
struct enable_if<false, T> { };
HAS_MEM_FUNC(toString, has_to_string);
template<typename T>
typename enable_if<has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T has toString ... */
return t->toString();
}
template<typename T>
typename enable_if<!has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T doesnt have toString ... */
return "T::toString() does not exist.";
}
Получайте удовольствие, используя его. Преимущество в том, что он также работает для перегруженных функций-членов, а также для константных функций-членов (не забывайте использовать std::string(T::*)() const
как тип указателя на функцию-член!).
Хотя этому вопросу два года, я позволю себе добавить свой ответ. Надеемся, что она будет разъяснено предыдущий, бесспорно отличный, решение. Я взял очень полезные ответы Николая Бонелли и Йоханнеса Шауба и объединил их в решение, которое, ИМХО, более читабельно, понятно и не требует typeof
расширение:
template <class Type>
class TypeHasToString
{
// This type won't compile if the second template parameter isn't of type T,
// so I can put a function pointer type in the first parameter and the function
// itself in the second thus checking that the function has a specific signature.
template <typename T, T> struct TypeCheck;
typedef char Yes;
typedef long No;
// A helper struct to hold the declaration of the function pointer.
// Change it if the function signature changes.
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(...) -> string
{
return "toString not defined";
}
Обновление, 3 года спустя: (и это не проверено). Чтобы проверить на существование, я думаю, что это будет работать:
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";
}
Ну, на этот вопрос уже есть длинный список ответов, но я хотел бы подчеркнуть комментарий Morwenn: есть предложение для C++17, которое делает его действительно намного проще. Подробности смотрите в N4502, но в качестве отдельного примера рассмотрите следующее.
Эта часть является постоянной частью, поместите ее в заголовок.
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
затем есть переменная часть, где вы указываете, что вы ищете (тип, тип члена, функцию, функцию-член и т. д.). В случае ОП:
template <typename T>
using toString_t = decltype(std::declval<T>().toString());
template <typename T>
using has_toString = detect<T, toString_t>;
Следующий пример, взятый из N4502, показывает более сложный зонд:
// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())
// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;
По сравнению с другими реализациями, описанными выше, эта довольно проста: сокращенный набор инструментов (void_t
а также detect
) достаточно, не нужно волосатых макросов. Кроме того, сообщалось (см. N4502), что он заметно более эффективен (время компиляции и потребление памяти компилятором), чем предыдущие подходы.
Вот живой пример. Он прекрасно работает с Clang, но, к сожалению, версии GCC до 5.1 следовали другой интерпретации стандарта C++11, что вызвало void_t
не работать, как ожидалось. Якк уже предоставил обходной путь: используйте следующее определение void_t
( void_t в списке параметров работает, но не как тип возвращаемого значения):
#if __GNUC__ < 5 && ! defined __clang__
// https://stackru.com/a/28967049/1353549
template <typename...>
struct voider
{
using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif
Вот для чего нужны черты типа. К сожалению, они должны быть определены вручную. В вашем случае представьте следующее:
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;
}
Еще один способ сделать это в C++17 (вдохновленный boost:hana).
Напишите один раз и используйте много раз. Не требуетhas_something<T>
классы черт типов.
////////////////////////////////////////////
// is_valid implementation
////////////////////////////////////////////
#include <type_traits>
template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }
template<typename>
constexpr bool is_valid(...) { return false; }
#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )
////////////////////////////////////////////
// Example
////////////////////////////////////////////
#include <iostream>
#include <string>
struct Example {
int Foo;
void Bar() {}
std::string toString() { return "Hello from toString()!"; }
};
struct Example2 {
int X;
};
template<class T>
std::string optionalToString(T* obj)
{
if constexpr(IS_VALID(T, toString()))
return obj->toString();
else
return "toString not defined";
}
int main() {
static_assert(IS_VALID(Example, Foo),
"Example class must have Foo member");
static_assert(IS_VALID(Example, Bar()),
"Example class must have Bar() member function");
static_assert(!IS_VALID(Example, ZFoo),
"Example class must not have ZFoo member.");
static_assert(!IS_VALID(Example, ZBar()),
"Example class must not have ZBar() member function");
Example e1;
Example2 e2;
std::cout << "e1: " << optionalToString(&e1) << "\n";
std::cout << "e1: " << optionalToString(&e2) << "\n";
}
Вот самый краткий способ, который я нашел в С ++20, который очень близок к вашему вопросу:
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (requires { obj->toString(); })
return obj->toString();
else
return "toString not defined";
}
Смотрите в прямом эфире на Godbolt: https://gcc.godbolt.org/z/5jb1d93Ms
Это решение C++11 для общей проблемы, если "Если бы я сделал X, он бы скомпилировал?"
template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
T,
type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};
Черта характера has_to_string
такой, что has_to_string<T>::value
является true
если и только если T
есть метод .toString
это может быть вызвано с 0 аргументами в этом контексте.
Далее я бы использовал диспетчеризацию тегов:
namespace details {
template<class T>
std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
return obj->toString();
}
template<class T>
std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
return "toString not defined";
}
}
template<class T>
std::string optionalToString(T* obj) {
return details::optionalToString_helper( obj, has_to_string<T>{} );
}
который имеет тенденцию быть более понятным, чем сложные выражения SFINAE.
Вы можете написать эти черты с помощью макроса, если вы обнаружите, что делаете это много, но они относительно просты (несколько строк каждая), поэтому, может быть, это того не стоит:
#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};
что выше, это создать макрос MAKE_CODE_TRAIT
, Вы передаете ему имя желаемой черты и некоторый код, который может проверить тип T
, Таким образом:
MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )
создает вышеупомянутый класс черт.
Кроме того, вышеупомянутая методика является частью того, что MS называет "выражением SFINAE", и их компилятор 2013 года терпит неудачу довольно сильно.
Обратите внимание, что в C++1y возможен следующий синтаксис:
template<class T>
std::string optionalToString(T* obj) {
return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
return obj.toString();
}) *compiled_else ([&]{
return "toString not defined";
});
}
которая является условной веткой встроенной компиляции, которая использует множество функций C++. Делать это, вероятно, не стоит, так как выгода (от встроенного кода) не стоит затрат (почти никто не понимает, как это работает), но может оказаться интересным наличие вышеупомянутого решения.
С C++ 20 вы можете написать следующее:
template<typename T>
concept has_toString = requires(const T& t) {
t.toString();
};
template<typename T>
std::string optionalToString(const T& obj)
{
if constexpr (has_toString<T>)
return obj.toString();
else
return "toString not defined";
}
Вот некоторые фрагменты использования: * Внутренности для всего этого находятся ниже
Проверить для участника x
в данном классе. Может быть var, func, class, union или enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
Проверьте функцию члена void x()
:
//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
Проверьте переменную члена x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
Проверьте для класса участника x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
Проверить членство в профсоюзе x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
Проверить перечисление членов x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
Проверьте любую функцию-член x
независимо от подписи:
CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
ИЛИ ЖЕ
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
Детали и ядро:
/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
//Variadic to force ambiguity of class members. C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};
//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};
template<typename A, typename = void>
struct got_type : std::false_type {};
template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};
template<typename T, T>
struct sig_check : std::true_type {};
template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
};
Макросы (Эль Диабло!):
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> { static const decltype(&T::member) value; }; \
\
struct AmbiguitySeed_##member { char member; }; \
\
template<typename T> \
struct has_member_##member { \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
}
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type {}; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type {}; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type {}
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type {}; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type {}; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type {}; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func { \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
}
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)
Я написал ответ на этот вопрос в другом потоке, который (в отличие от решений выше) также проверяет унаследованные функции-члены:
SFINAE для проверки унаследованных функций-членов
Вот несколько примеров из этого решения:
Example1:
Мы проверяем участника со следующей подписью: T::const_iterator begin() const
template<class T> struct has_const_begin
{
typedef char (&Yes)[1];
typedef char (&No)[2];
template<class U>
static Yes test(U const * data,
typename std::enable_if<std::is_same<
typename U::const_iterator,
decltype(data->begin())
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};
Обратите внимание, что он даже проверяет константность метода, а также работает с примитивными типами. (Я имею в виду has_const_begin<int>::value
ложно и не вызывает ошибку во время компиляции.)
Пример 2
Теперь мы ищем подпись: void foo(MyClass&, unsigned)
template<class T> struct has_foo
{
typedef char (&Yes)[1];
typedef char (&No)[2];
template<class U>
static Yes test(U * data, MyClass* arg1 = 0,
typename std::enable_if<std::is_void<
decltype(data->foo(*arg1, 1u))
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};
Обратите внимание, что MyClass не должен быть конструируемым по умолчанию или соответствовать какой-либо специальной концепции. Техника работает и с членами шаблона.
Я с нетерпением жду мнения по этому поводу.
Теперь это была хорошая маленькая загадка - отличный вопрос!
Вот альтернатива решению Никола Бонелли, которое не опирается на нестандартные typeof
оператор.
К сожалению, он не работает на GCC (MinGW) 3.4.5 или Digital Mars 8.42n, но он работает на всех версиях MSVC (включая VC6) и на Comeau C++.
Более длинный блок комментариев содержит подробности о том, как он работает (или должен работать). Как говорится, я не уверен, какое поведение соответствует стандартам - я хотел бы получить комментарии по этому поводу.
обновление - 7 ноября 2008 г.:
Похоже, что в то время как этот код синтаксически корректен, поведение, которое демонстрируют MSVC и Comeau C++, не соответствует стандарту (спасибо Leon Timmermans и Johannes Schaub - litb за указание на правильное направление). Стандарт C++03 гласит следующее:
14.6.2 Зависимые имена [temp.dep]
Пункт 3
В определении шаблона класса или члена шаблона класса, если базовый класс шаблона класса зависит от параметра-шаблона, область действия базового класса не проверяется при поиске неквалифицированного имени ни в точке определения класса шаблон или член или во время создания шаблона класса или члена.
Итак, похоже, что когда MSVC или Comeau рассматривают toString()
функция-член T
выполнение поиска имени на сайте вызова в doToString()
когда создается экземпляр шаблона, это неверно (даже при том, что на самом деле это поведение, которое я искал в этом случае).
Поведение GCC и Digital Mars выглядит корректно - в обоих случаях нечлен toString()
Функция связана с вызовом.
Крысы - я думал, что мог бы найти умное решение, вместо этого я обнаружил пару ошибок компилятора...
#include <iostream>
#include <string>
struct Hello
{
std::string toString() {
return "Hello";
}
};
struct Generic {};
// the following namespace keeps the toString() method out of
// most everything - except the other stuff in this
// compilation unit
namespace {
std::string toString()
{
return "toString not defined";
}
template <typename T>
class optionalToStringImpl : public T
{
public:
std::string doToString() {
// in theory, the name lookup for this call to
// toString() should find the toString() in
// the base class T if one exists, but if one
// doesn't exist in the base class, it'll
// find the free toString() function in
// the private namespace.
//
// This theory works for MSVC (all versions
// from VC6 to VC9) and Comeau C++, but
// does not work with MinGW 3.4.5 or
// Digital Mars 8.42n
//
// I'm honestly not sure what the standard says
// is the correct behavior here - it's sort
// of like ADL (Argument Dependent Lookup -
// also known as Koenig Lookup) but without
// arguments (except the implied "this" pointer)
return toString();
}
};
}
template <typename T>
std::string optionalToString(T & obj)
{
// ugly, hacky cast...
optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);
return temp->doToString();
}
int
main(int argc, char *argv[])
{
Hello helloObj;
Generic genericObj;
std::cout << optionalToString( helloObj) << std::endl;
std::cout << optionalToString( genericObj) << std::endl;
return 0;
}
Стандартное решение C++, представленное здесь litb, не будет работать должным образом, если метод будет определен в базовом классе.
Для решения, которое обрабатывает эту ситуацию, обратитесь к:
На русском языке: http://www.rsdn.ru/forum/message/2759773.1.aspx
Перевод с английского Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1
Это безумно умно. Однако одна проблема с этим решением заключается в том, что выдает ошибки компилятора, если тестируемый тип не может использоваться в качестве базового класса (например, примитивные типы).
В Visual Studio я заметил, что при работе с методом, не имеющим аргументов, необходимо добавить дополнительную пару избыточных () вокруг аргументов для вывода () в выражении sizeof.
Пример использования SFINAE и частичной специализации шаблона, написав Has_foo
проверка концепции:
#include <type_traits>
struct A{};
struct B{ int foo(int a, int b);};
struct C{void foo(int a, int b);};
struct D{int foo();};
struct E: public B{};
// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;
template<typename T, typename = void> struct Has_foo: std::false_type{};
template<typename T>
struct Has_foo<T, void_t<
std::enable_if_t<
std::is_same<
int,
decltype(std::declval<T>().foo((int)0, (int)0))
>::value
>
>>: std::true_type{};
static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");
Я знаю, что этому вопросу много лет, но я думаю, что для таких людей, как я, было бы полезно получить более полный обновленный ответ, который также работает для
const
перегруженные методы, такие как
std::vector<>::begin
.
Основываясь на этом ответе и ответе на мой последующий вопрос, вот более полный ответ. Обратите внимание, что это будет работать только с C++11 и выше.
#include <iostream>
#include <vector>
class EmptyClass{};
template <typename T>
class has_begin
{
private:
has_begin() = delete;
struct one { char x[1]; };
struct two { char x[2]; };
template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * ) ;
template <typename C> static two test(...);
public:
static constexpr bool value = sizeof(test<T>(0)) == sizeof(one);
};
int main(int argc, char *argv[])
{
std::cout << std::boolalpha;
std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
std::cout << "EmptyClass::begin() exists: " << has_begin<EmptyClass>::value << std::endl;
return 0;
}
Или более короткая версия:
#include <iostream>
#include <vector>
class EmptyClass{};
template <typename T, typename = void>
struct has_begin : std::false_type {};
template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};
int main(int argc, char *argv[])
{
std::cout << std::boolalpha;
std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
std::cout << "EmptyClass exists: " << has_begin<EmptyClass>::value << std::endl;
}
Note that here a complete sample call must be provided. This means that if we tested for the
resize
method's existence then we would have put
resize(0)
.
Deep magic explanation:
The first answer posted of this question used
test( decltype(&C::helloworld) )
; however this is problematic when the method it is testing is ambiguous due const overloading, thus making the substitution attempt fail.
To solve this ambiguity we use a void statement which can take any parameters because it is always translated into a
noop
and thus the ambiguity is nullified and the call is valid as long as the method exists:
has_begin<T, decltype(void(std::declval<T &>().begin()))>
Here's what's happening in order:We use
std::declval<T &>()
to create a callable value for which
begin
can then be called. After that the value of
begin
is passed as a parameter to a void statement. We then retrieve the type of that void expression using the builtin
decltype
so that it can be used as a template type argument. If
begin
doesn't exist then the substitution is invalid and as per SFINAE the other declaration is used instead.
Я изменил решение, представленное на /questions/19900712/mozhno-li-napisat-shablon-dlya-proverki-suschestvovaniya-funktsii/19900724#19900724 чтобы сделать его более общим. Кроме того, поскольку он не использует какие-либо новые функции C++11, мы можем использовать его со старыми компиляторами и также должны работать с msvc. Но компиляторы должны позволить C99 использовать это, поскольку он использует макросы с переменным числом аргументов.
Следующий макрос можно использовать для проверки, имеет ли определенный класс определенный typedef или нет.
/**
* @class : HAS_TYPEDEF
* @brief : This macro will be used to check if a class has a particular
* typedef or not.
* @param typedef_name : Name of Typedef
* @param name : Name of struct which is going to be run the test for
* the given particular typedef specified in typedef_name
*/
#define HAS_TYPEDEF(typedef_name, name) \
template <typename T> \
struct name { \
typedef char yes[1]; \
typedef char no[2]; \
template <typename U> \
struct type_check; \
template <typename _1> \
static yes& chk(type_check<typename _1::typedef_name>*); \
template <typename> \
static no& chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
Следующий макрос можно использовать для проверки, имеет ли определенный класс конкретную функцию-член или нет с любым заданным количеством аргументов.
/**
* @class : HAS_MEM_FUNC
* @brief : This macro will be used to check if a class has a particular
* member function implemented in the public section or not.
* @param func : Name of Member Function
* @param name : Name of struct which is going to be run the test for
* the given particular member function name specified in func
* @param return_type: Return type of the member function
* @param ellipsis(...) : Since this is macro should provide test case for every
* possible member function we use variadic macros to cover all possibilities
*/
#define HAS_MEM_FUNC(func, name, return_type, ...) \
template <typename T> \
struct name { \
typedef return_type (T::*Sign)(__VA_ARGS__); \
typedef char yes[1]; \
typedef char no[2]; \
template <typename U, U> \
struct type_check; \
template <typename _1> \
static yes& chk(type_check<Sign, &_1::func>*); \
template <typename> \
static no& chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
Мы можем использовать вышеупомянутые 2 макроса, чтобы выполнить проверки для has_typedef и has_mem_func как:
class A {
public:
typedef int check;
void check_function() {}
};
class B {
public:
void hello(int a, double b) {}
void hello() {}
};
HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);
int main() {
std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}
Как насчет этого решения?
#include <type_traits>
template <typename U, typename = void> struct hasToString : std::false_type { };
template <typename U>
struct hasToString<U,
typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };
Странно, никто не предложил следующий хороший трюк, который я видел однажды на этом сайте:
template <class T>
struct has_foo
{
struct S { void foo(...); };
struct derived : S, T {};
template <typename V, V> struct W {};
template <typename X>
char (&test(W<void (X::*)(), &X::foo> *))[1];
template <typename>
char (&test(...))[2];
static const bool value = sizeof(test<derived>(0)) == 1;
};
Вы должны убедиться, что T является классом. Кажется, что двусмысленность в поиске foo является ошибкой замещения. Я заставил его работать на gcc, но не уверен, что он стандартный.
Общий шаблон, который можно использовать для проверки, поддерживается ли какая-либо "функция" типом:
#include <type_traits>
template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
// these structs are used to recognize which version
// of the two functions was chosen during overload resolution
struct supported {};
struct not_supported {};
// this overload of chk will be ignored by SFINAE principle
// if TypeChecker<Type_> is invalid type
template <typename Type_>
static supported chk(typename std::decay<TypeChecker<Type_>>::type *);
// ellipsis has the lowest conversion rank, so this overload will be
// chosen during overload resolution only if the template overload above is ignored
template <typename Type_>
static not_supported chk(...);
// if the template overload of chk is chosen during
// overload resolution then the feature is supported
// if the ellipses overload is chosen the the feature is not supported
static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};
Шаблон, который проверяет, есть ли метод foo
что совместимо с подписью double(const char*)
// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));
Примеры
// types that support has_foo
struct struct1 { double foo(const char*); }; // exact signature match
struct struct2 { int foo(const std::string &str); }; // compatible signature
struct struct3 { float foo(...); }; // compatible ellipsis signature
struct struct4 { template <typename T>
int foo(T t); }; // compatible template signature
// types that do not support has_foo
struct struct5 { void foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double foo( int *); }; // const char* can't be converted to int*
struct struct8 { double bar(const char*); }; // there is no foo method
int main()
{
std::cout << std::boolalpha;
std::cout << is_supported<has_foo, int >::value << std::endl; // false
std::cout << is_supported<has_foo, double >::value << std::endl; // false
std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
std::cout << is_supported<has_foo, struct4>::value << std::endl; // true
std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
std::cout << is_supported<has_foo, struct8>::value << std::endl; // false
return 0;
}
Мое мнение: универсально определить, можно ли что-то вызвать, без подробных характеристик типа для каждого и каждого, без использования экспериментальных функций или длинного кода:
template<typename Callable, typename... Args, typename = decltype(declval<Callable>()(declval<Args>()...))>
std::true_type isCallableImpl(Callable, Args...) { return {}; }
std::false_type isCallableImpl(...) { return {}; }
template<typename... Args, typename Callable>
constexpr bool isCallable(Callable callable) {
return decltype(isCallableImpl(callable, declval<Args>()...)){};
}
Применение:
constexpr auto TO_STRING_TEST = [](auto in) -> decltype(in.toString()) { return {}; };
constexpr bool TO_STRING_WORKS = isCallable<T>(TO_STRING_TEST);
Здесь есть много ответов, но мне не удалось найти версию, которая выполняет реальное упорядочение разрешения метода, не используя какие-либо новые функции C++ (только с использованием функций C++98).
Примечание. Эта версия протестирована и работает с vC++2013, g++ 5.2.0 и онлайн-компилятором.
Итак, я придумал версию, которая использует только sizeof():
template<typename T> T declval(void);
struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);
struct yes { char v[1]; };
struct no { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};
template<typename T>
struct has_awesome_member {
template<typename U> static yes_no<(sizeof((
declval<U>().awesome_member(),fake_void()
))!=0)> check(int);
template<typename> static no check(...);
enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};
struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };
static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");
Демонстрационная версия (с расширенной проверкой типов возвращаемых данных и обходным путем vC++2010): http://cpp.sh/5b2vs
Нет источника, так как сам придумал.
При запуске демонстрации Live на компиляторе g ++ обратите внимание, что допустимы размеры массива 0, что означает, что используемый static_assert не вызовет ошибку компилятора, даже если он завершится с ошибкой.
Обычно используемый обходной путь заключается в замене "typedef" в макросе на "extern".
Возможно, не так хорошо, как другие примеры, но это то, что я придумал для C++11. Это работает для выбора перегруженных методов.
template <typename... Args>
struct Pack {};
#define Proxy(T) ((T &)(*(int *)(nullptr)))
template <typename Class, typename ArgPack, typename = nullptr_t>
struct HasFoo
{
enum { value = false };
};
template <typename Class, typename... Args>
struct HasFoo<
Class,
Pack<Args...>,
decltype((void)(Proxy(Class).foo(Proxy(Args)...)), nullptr)>
{
enum { value = true };
};
Пример использования
struct Object
{
int foo(int n) { return n; }
#if SOME_CONDITION
int foo(int n, char c) { return n + c; }
#endif
};
template <bool has_foo_int_char>
struct Dispatcher;
template <>
struct Dispatcher<false>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n) + c;
}
};
template <>
struct Dispatcher<true>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n, c);
}
};
int runExample()
{
using Args = Pack<int, char>;
enum { has_overload = HasFoo<Object, Args>::value };
Object object;
return Dispatcher<has_overload>::exec(object, 100, 'a');
}
Вы можете пропустить все метапрограммирование в C++14 и просто написать это, используя fit::conditional
из библиотеки Fit:
template<class T>
std::string optionalToString(T* x)
{
return fit::conditional(
[](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
[](auto*) { return "toString not defined"; }
)(x);
}
Вы также можете создать функцию непосредственно из лямбд:
FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
[](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
[](auto*) -> std::string { return "toString not defined"; }
);
Однако, если вы используете компилятор, который не поддерживает общие лямбда-выражения, вам придется написать отдельные функциональные объекты:
struct withToString
{
template<class T>
auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
{
return obj->toString();
}
};
struct withoutToString
{
template<class T>
std::string operator()(T*) const
{
return "toString not defined";
}
};
FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
withToString(),
withoutToString()
);
Вот моя версия, которая обрабатывает все возможные перегрузки функций-членов с произвольной арностью, включая функции-члены шаблона, возможно, с аргументами по умолчанию. Он различает 3 взаимоисключающих сценария при вызове функции-члена некоторого типа класса с заданными типами аргументов: (1) допустимый, (2) неоднозначный или (3) нежизнеспособный. Пример использования:
#include <string>
#include <vector>
HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)
struct test
{
void bar(int);
void bar(double);
void bar(int,double);
template < typename T >
typename std::enable_if< not std::is_integral<T>::value >::type
bar(const T&, int=0){}
template < typename T >
typename std::enable_if< std::is_integral<T>::value >::type
bar(const std::vector<T>&, T*){}
template < typename T >
int bar(const std::string&, int){}
};
Теперь вы можете использовать это так:
int main(int argc, const char * argv[])
{
static_assert( has_mem_bar<test>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");
static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");
static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");
static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");
return 0;
}
Вот код, написанный на C++11, однако вы можете легко перенести его (с небольшими изменениями) на не-C++11, который имеет расширения typeof (например, gcc). Вы можете заменить макрос HAS_MEM своим собственным.
#pragma once
#if __cplusplus >= 201103
#include <utility>
#include <type_traits>
#define HAS_MEM(mem) \
\
template < typename T > \
struct has_mem_##mem \
{ \
struct yes {}; \
struct no {}; \
\
struct ambiguate_seed { char mem; }; \
template < typename U > struct ambiguate : U, ambiguate_seed {}; \
\
template < typename U, typename = decltype(&U::mem) > static constexpr no test(int); \
template < typename > static constexpr yes test(...); \
\
static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ; \
typedef std::integral_constant<bool,value> type; \
};
#define HAS_MEM_FUN_CALL(memfun) \
\
template < typename Signature > \
struct has_valid_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_valid_mem_fun_call_##memfun< T(Args...) > \
{ \
struct yes {}; \
struct no {}; \
\
template < typename U, bool = has_mem_##memfun<U>::value > \
struct impl \
{ \
template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
struct test_result { using type = yes; }; \
\
template < typename V > static constexpr typename test_result<V>::type test(int); \
template < typename > static constexpr no test(...); \
\
static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename U > \
struct impl<U,false> : std::false_type {}; \
\
static constexpr bool value = impl<T>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_ambiguous_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) > \
{ \
struct ambiguate_seed { void memfun(...); }; \
\
template < class U, bool = has_mem_##memfun<U>::value > \
struct ambiguate : U, ambiguate_seed \
{ \
using ambiguate_seed::memfun; \
using U::memfun; \
}; \
\
template < class U > \
struct ambiguate<U,false> : ambiguate_seed {}; \
\
static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_viable_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_viable_mem_fun_call_##memfun< T(Args...) > \
{ \
static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value \
or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_no_viable_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) > \
{ \
static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct result_of_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct result_of_mem_fun_call_##memfun< T(Args...) > \
{ \
using type = decltype(std::declval<T>().memfun(std::declval<Args>()...)); \
};
#endif
С++03 способ
#define HasMember(NAME) \
template<class Class, typename Type = void> \
struct HasMember_##NAME \
{ \
typedef char (&yes)[2]; \
template<unsigned long> struct exists; \
template<typename V> static yes Check (exists<sizeof(static_cast<Type>(&V::NAME))>*); \
template<typename> static char Check (...); \
static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \
}; \
template<class Class> \
struct HasMember_##NAME<Class, void> \
{ \
typedef char (&yes)[2]; \
template<unsigned long> struct exists; \
template<typename V> static yes Check (exists<sizeof(&V::NAME)>*); \
template<typename> static char Check (...); \
static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \
}
Используя приведенный выше макрос, вы можете найти существование любого члена в классе, будь то переменная или метод. Если есть два метода с одинаковым именем, нам также необходимо указать тип метода.
Использование :
#include<iostream>
struct S
{
void Foo () const {}
// void Foo () {} // If uncommented then, SFINAE fails
int i;
};
HasMember(Foo);
HasMember(i);
int main ()
{
std::cout << HasMember_Foo<S, void (S::*) () const>::value << "\n";
std::cout << HasMember_Foo<S>::value << "\n";
std::cout << HasMember_i<S, int (S::*)>::value << "\n";
std::cout << HasMember_i<S>::value << "\n";
}
Второй может напечатать 0, если есть 2 метода с одинаковым именем.Foo
присутствует вS
. В случае переменной-члена тип (как указано в третьем пункте)cout
) является избыточным, поскольку имя переменной может иметь только 1. Однако для проверки конкретного типа это полезно (как для метода, так и для переменной).
Вот пример рабочего кода.
template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());
template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
return obj->toString();
}
template <class T>
std::string optionalToString(const T* obj, long)
{
return "toString not defined";
}
int main()
{
A* a;
B* b;
std::cout << optionalToString(a, 0) << std::endl; // This is A
std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}
toStringFn<T>* = nullptr
включит функцию, которая принимает дополнительные int
аргумент, который имеет приоритет над функцией, которая принимает long
когда вызывается с 0
,
Вы можете использовать тот же принцип для функций, которые возвращают true
если функция реализована.
template <typename T>
constexpr bool toStringExists(long)
{
return false;
}
template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
return true;
}
int main()
{
A* a;
B* b;
std::cout << toStringExists<A>(0) << std::endl; // true
std::cout << toStringExists<B>(0) << std::endl; // false
}