Проверьте, имеет ли класс функцию-член с заданной сигнатурой
Я прошу шаблонный трюк, чтобы определить, имеет ли класс конкретную функцию-член с заданной сигнатурой.
Эта проблема аналогична той, что приведена здесь http://www.gotw.ca/gotw/071.htm но не та же: в пункте книги Саттера он ответил на вопрос, что класс C ДОЛЖЕН ПРЕДОСТАВЛЯТЬ функцию-член с конкретная подпись, иначе программа не скомпилируется. В моей задаче мне нужно что-то делать, если у класса есть эта функция, иначе делать "что-то еще".
С аналогичной проблемой столкнулся boost::serialization, но мне не нравится принятое ими решение: шаблонная функция, которая по умолчанию вызывает свободную функцию (которую вы должны определить) с определенной сигнатурой, если вы не определите конкретную функцию-член (в их случае "serialize", который принимает 2 параметра данного типа) с определенной сигнатурой, иначе произойдет ошибка компиляции. То есть реализовать как навязчивую, так и не навязчивую сериализацию.
Мне не нравится это решение по двум причинам:
- Чтобы не быть навязчивым, вы должны переопределить глобальную функцию "сериализации", которая находится в пространстве имен boost::serialization, поэтому у вас есть В НАШЕМ КЛИЕНТЕ КОДЕ, чтобы открыть повышение пространства имен и сериализацию пространства имен!
- Стек для устранения этого беспорядка состоял из 10-12 вызовов функций.
Мне нужно определить пользовательское поведение для классов, которые не имеют этой функции-члена, и мои сущности находятся в разных пространствах имен (и я не хочу переопределять глобальную функцию, определенную в одном пространстве имен, пока я нахожусь в другом)
Можете ли вы дать мне подсказку, чтобы решить эту загадку?
13 ответов
Я не уверен, правильно ли я вас понимаю, но вы можете использовать SFINAE для обнаружения присутствия функций во время компиляции. Пример из моего кода (проверяет, есть ли в классе функция-член size_t used_memory() const).
template<typename T>
struct HasUsedMemoryMethod
{
template<typename U, size_t (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};
template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
// We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
ReportMemUsage(m,
std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
Вот возможная реализация, основанная на возможностях C++11. Он правильно определяет функцию, даже если она унаследована (в отличие от решения в принятом ответе, как замечает Майк Кингхан в своем ответе).
Функция, которую проверяет этот фрагмент, называется serialize
:
#include <type_traits>
// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.
template<typename, typename T>
struct has_serialize {
static_assert(
std::integral_constant<T, false>::value,
"Second template parameter needs to be of function type.");
};
// specialization that does the checking
template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
template<typename T>
static constexpr auto check(T*)
-> typename
std::is_same<
decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>::type; // attempt to call it and see if the return type is correct
template<typename>
static constexpr std::false_type check(...);
typedef decltype(check<C>(0)) type;
public:
static constexpr bool value = type::value;
};
Использование:
struct X {
int serialize(const std::string&) { return 42; }
};
struct Y : X {};
std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
Принятый ответ на этот вопрос самоанализа функции-члена во время компиляции, хотя он и справедливо популярен, имеет загвоздку, которую можно наблюдать в следующей программе:
#include <type_traits>
#include <iostream>
#include <memory>
/* Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
using namespace std;
/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}
Построен с GCC 4.6.3, программа выводит 110
- сообщая нам, что T = std::shared_ptr<int>
не обеспечивает int & T::operator*() const
,
Если вы еще не мудры в этом, то взгляните на определениеstd::shared_ptr<T>
в шапке <memory>
пролить свет. В этой реализации, std::shared_ptr<T>
происходит от базового класса, от которого он наследуется operator*() const
, Таким образом, шаблон созданияSFINAE<U, &U::operator*>
что составляет "поиск" оператора дляU = std::shared_ptr<T>
не произойдет, потому что std::shared_ptr<T>
не имеетoperator*()
в своем собственном праве и шаблонной реализации не "делать наследование".
Эта загвоздка не влияет на хорошо известный подход SFINAE, использующий "уловку sizeof()", для определения просто T
имеет некоторую функцию-член mf
(см., например, этот ответ и комментарии). Но установив, что T::mf
существует часто (обычно?) недостаточно хорошо: вам также может потребоваться установить, что он имеет желаемую подпись. Вот где проиллюстрирована техника. Указанный вариант требуемой подписи вписывается в параметр типа шаблона, который должен удовлетворяться&T::mf
для зонда SFINAE, чтобы преуспеть. Но этот метод создания шаблона дает неправильный ответ, когда T::mf
наследуется
Безопасный метод SFINAE для самоанализа во время компиляции T::mf
должны избегать использования &T::mf
внутри аргумента шаблона для создания экземпляра типа, от которого зависит разрешение шаблона функции SFINAE. Вместо этого разрешение функции шаблона SFINAE может зависеть только от точно соответствующих объявлений типов, используемых в качестве типов аргументов перегруженной функции зонда SFINAE.
В качестве ответа на вопрос, который соблюдает это ограничение, я проиллюстрирую это при обнаружении во время компиляции E T::operator*() const
для произвольного T
а также E
, Тот же шаблон будет применяться mutatis mutandis для проверки любой другой сигнатуры метода-члена.
#include <type_traits>
/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}
/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
В этом решении перегружена функция зонда SFINAE. test()
"вызывается рекурсивно". (Конечно, на самом деле он вообще не вызывается; у него просто есть возвращаемые типы гипотетических вызовов, разрешенных компилятором.)
Нам нужно исследовать как минимум одну и максимум две точки информации:
- Есть ли
T::operator*()
существуют вообще? Если нет, мы сделали. - При условии
T::operator*()
существует, есть его подписьE T::operator*() const
?
Мы получаем ответы, оценивая тип возврата одного звонка test(0,0)
, Это сделано:
typedef decltype(test<T>(0,0)) type;
Этот вызов может быть разрешен /* SFINAE operator-exists :) */
перегрузка test()
или это может разрешить /* SFINAE game over :( */
перегрузки. Это не может разрешить /* SFINAE operator-has-correct-sig :) */
перегрузка, потому что этот ожидает только один аргумент, а мы передаем два.
Почему мы проходим два? Просто заставить резолюцию исключить /* SFINAE operator-has-correct-sig :) */
, Второй аргумент не имеет никакого другого значения.
Этот призыв к test(0,0)
будет разрешать /* SFINAE operator-exists :) */
на всякий случай первый аргумент 0 удовлетворяет первый тип параметра этой перегрузки, который decltype(&A::operator*)
, с A = T
, 0 удовлетворит этот тип на всякий случай T::operator*
существует.
Давайте предположим, что компилятор с этим согласен. Тогда это происходит с/* SFINAE operator-exists :) */
и он должен определить тип возвращаемого значения вызова функции, который в этом случае decltype(test(&A::operator*))
- тип возврата еще одного вызова test()
,
На этот раз мы передаем только один аргумент, &A::operator*
, который мы теперь знаем, существует, иначе нас бы здесь не было. Вызов test(&A::operator*)
может решить либо /* SFINAE operator-has-correct-sig :) */
или снова может разрешить /* SFINAE game over :( */
, Вызов будет соответствовать /* SFINAE operator-has-correct-sig :) */
так, на всякий случай &A::operator*
удовлетворяет единственный тип параметра этой перегрузки, который E (A::*)() const
, с A = T
,
Компилятор скажет Да, если здесь T::operator*
имеет желаемую сигнатуру, а затем снова должен оценить тип возвращаемого значения перегрузки. Больше нет "рекурсий": это std::true_type
,
Если компилятор не выбирает /* SFINAE operator-exists :) */
для вызова test(0,0)
или не выбирает /* SFINAE operator-has-correct-sig :) */
для вызова test(&A::operator*)
то в любом случае/* SFINAE game over :( */
и окончательный тип возврата std::false_type
,
Вот тестовая программа, которая показывает шаблон, дающий ожидаемые ответы в различных выборках (снова GCC 4.6.3).
// To test
struct empty{};
// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};
// To test
struct sub_int_ref : int_ref{};
// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};
// To test
struct sub_ee_ref : ee_ref<char>{};
using namespace std;
#include <iostream>
#include <memory>
#include <vector>
int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value << endl;
return 0;
}
Есть ли в этой идее новые недостатки? Можно ли сделать его более общим, не нарушая при этом препятствия, которого он избегает?
Вот некоторые фрагменты использования: * Внутренности для всего этого находятся ниже
Проверить для участника 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)
С С ++ 20 это становится намного проще. Допустим, мы хотим проверить,
T
имеет функцию-член
void T::resize(typename T::size_type)
. Например,
std::vector<U>
имеет такую функцию-член. Потом,
template<typename T>
concept has_resize_member_func = requires {
typename T::size_type;
{ std::declval<T>().resize(std::declval<typename T::size_type>()) } -> std::same_as<void>;
};
и использование
static_assert(has_resize_member_func<std::string>, "");
static_assert(has_resize_member_func<int> == false, "");
Этого должно быть достаточно, если вы знаете имя ожидаемой функции-члена. (В этом случае функция bla не может быть реализована, если нет функции-члена (написать ту, которая работает в любом случае сложно, потому что отсутствует частичная специализация функции. Возможно, вам придется использовать шаблоны классов). Также, структура включения (которая аналогичен enable_if) также может быть основан на типе функции, которую вы хотите иметь в качестве члена.
template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
A a;
B b;
bla(b);
bla(a);
}
Вот более простой ответ на вопрос Майка Кингхана. Это обнаружит унаследованные методы. Он также проверит точную подпись (в отличие от подхода jrok, который допускает преобразование аргументов).
template <class C>
class HasGreetMethod
{
template <class T>
static std::true_type testSignature(void (T::*)(const char*) const);
template <class T>
static decltype(testSignature(&T::greet)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");
Runnable пример
Кажется, вам нужна идиома детектора. Приведенные выше ответы являются вариантами этого, которые работают с C++11 или C++14.
В std::experimental
В библиотеке есть функции, которые по сути делают это. Перерабатывая пример сверху, это может быть:
#include <experimental/type_traits>
// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));
// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;
Если вы не можете использовать std:: экспериментальный, элементарную версию можно сделать следующим образом:
template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};
// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));
// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;
Поскольку has_serialize_t на самом деле является либо std:: true_type, либо std:: false_type, его можно использовать с помощью любой из распространенных идиом SFINAE:
template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}
Или с помощью отправки с разрешением перегрузки:
template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
// call serialize here.
}
template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
// do something else here.
}
template<class T>
std::string Serialize(const T& t) {
return SerializeImpl(has_serialize_t<T>{}, t);
}
Я сам столкнулся с такой же проблемой и нашел предлагаемые решения очень интересными... но имел требование к решению, которое:
- Обнаруживает также унаследованные функции;
- Совместим с готовыми компиляторами, не поддерживающими C++11 (поэтому нет decltype)
Нашел другой поток, предлагающий что-то вроде этого, на основе обсуждения BOOST. Вот обобщение предложенного решения в виде объявления двух макросов для класса признаков, следуя модели классов boost::has_ *.
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>
/// Has constant function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)
/// Has non-const function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)
// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...) \
template \
< typename Type, \
bool is_class = boost::is_class<Type>::value \
> \
class has_func_ ## func_name; \
template<typename Type> \
class has_func_ ## func_name<Type,false> \
{public: \
BOOST_STATIC_CONSTANT( bool, value = false ); \
typedef boost::false_type type; \
}; \
template<typename Type> \
class has_func_ ## func_name<Type,true> \
{ struct yes { char _foo; }; \
struct no { yes _foo[2]; }; \
struct Fallback \
{ func_ret_type func_name( __VA_ARGS__ ) \
UTILITY_OPTIONAL(func_const,const) {} \
}; \
struct Derived : public Type, public Fallback {}; \
template <typename T, T t> class Helper{}; \
template <typename U> \
static no deduce(U*, Helper \
< func_ret_type (Fallback::*)( __VA_ARGS__ ) \
UTILITY_OPTIONAL(func_const,const), \
&U::func_name \
>* = 0 \
); \
static yes deduce(...); \
public: \
BOOST_STATIC_CONSTANT( \
bool, \
value = sizeof(yes) \
== sizeof( deduce( static_cast<Derived*>(0) ) ) \
); \
typedef ::boost::integral_constant<bool,value> type; \
BOOST_STATIC_CONSTANT(bool, is_const = func_const); \
typedef func_ret_type return_type; \
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type; \
}
// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__
Эти макросы расширяются до класса признаков со следующим прототипом:
template<class T>
class has_func_[func_name]
{
public:
/// Function definition result value
/** Tells if the tested function is defined for type T or not.
*/
static const bool value = true | false;
/// Function definition result type
/** Type representing the value attribute usable in
http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
*/
typedef boost::integral_constant<bool,value> type;
/// Tested function constness indicator
/** Indicates if the tested function is const or not.
This value is not deduced, it is forced depending
on the user call to one of the traits generators.
*/
static const bool is_const = true | false;
/// Tested function return type
/** Indicates the return type of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef func_ret_type return_type;
/// Tested function arguments types
/** Indicates the arguments types of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};
Итак, что типичное использование можно сделать из этого?
// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
// Next line will declare the traits class
// to detect the member function void foo(int,int) const
DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}
// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>
// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{ _this_.foo(a,b);
}
// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{ default_foo(_this_,a,b);
}
// Let us declare test types
struct empty
{
};
struct direct_foo
{
void foo(int,int);
};
struct direct_const_foo
{
void foo(int,int) const;
};
struct inherited_const_foo :
public direct_const_foo
{
};
// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
int a;
foo_bar(a); // calls default_foo
empty b;
foo_bar(b); // calls default_foo
direct_foo c;
foo_bar(c); // calls default_foo (member function is not const)
direct_const_foo d;
foo_bar(d); // calls d.foo (member function is const)
inherited_const_foo e;
foo_bar(e); // calls e.foo (inherited member function)
}
Вы можете использовать std::is_member_function_pointer
class A {
public:
void foo() {};
}
bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
Для этого нам нужно использовать:
- Перегрузка шаблона функции различными типами возврата в зависимости от того, доступен ли метод
- В соответствии с мета-условиями в
type_traits
заголовок, мы хотим вернутьtrue_type
или жеfalse_type
от наших перегрузок - Объявить
true_type
перегрузка в ожиданииint
иfalse_type
перегрузка, ожидающая использования параметров Variadic: "Самый низкий приоритет преобразования эллипса в разрешении перегрузки" - При определении спецификации шаблона для
true_type
функция, которую мы будем использоватьdeclval
а такжеdecltype
что позволяет нам определять функцию независимо от различий в типах возвращаемых данных или перегрузок между методами
Вы можете увидеть живой пример этого здесь, но я объясню это ниже:
Я хочу проверить существование функции с именем test
который принимает конвертируемый тип из int
тогда мне нужно объявить эти две функции:
template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
decltype(hasTest<a>(0))::value
являетсяtrue
(Обратите внимание, что нет необходимости создавать специальные функции для решенияvoid a::test()
перегрузка,void a::test(int)
принято)decltype(hasTest<b>(0))::value
являетсяtrue
(Так какint
конвертируется вdouble
int b::test(double)
принимается независимо от типа возвращаемого значения)decltype(hasTest<c>(0))::value
являетсяfalse
(c
не имеет метода с именемtest
который принимает конвертируемый тип изint
для этого это не принято)
Это решение имеет 2 недостатка:
- Требуется объявление метода для пары функций
- Создает загрязнение пространства имен, особенно если мы хотим проверить похожие имена, например, что бы мы назвали функцией, которая хотела бы проверить
test()
метод?
Поэтому важно, чтобы эти функции были объявлены в пространстве имен деталей, или, в идеале, если они должны использоваться только с классом, они должны быть объявлены этим классом в частном порядке. Для этого я написал макрос, который поможет вам абстрагировать эту информацию:
#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
template <typename T> static false_type __ ## DEFINE(...); \
template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));
Вы можете использовать это как:
namespace details {
FOO(test(declval<int>()), test_int)
FOO(test(), test_void)
}
Впоследствии звоню details::test_int<a>::value
или же details::test_void<a>::value
даст true
или же false
для целей встроенного кода или метапрограммирования.
Чтобы быть ненавязчивым, вы также можете поставить serialize
в пространстве имен сериализуемого класса или класса архива, благодаря поиску Кенига. Посмотрите Пространства имен для Свободных Переопределений функций для получения дополнительной информации.:-)
Открытие любого заданного пространства имен для реализации бесплатной функции просто неверно. (например, вы не должны открывать пространство имен std
реализовать swap
для ваших собственных типов, но вместо этого следует использовать поиск Кенига.)
У меня была аналогичная потребность, и я столкнулся с этим ТАК. Здесь предлагается много интересных / мощных решений, хотя это немного длинно для конкретной потребности: определить, есть ли у класса функция-член с точной сигнатурой. Так что я немного прочитал / протестировал и придумал свою версию, которая могла бы заинтересовать. Он обнаруживает:
- статическая функция-член
- нестатическая функция-член
- нестатическая функция-член const
с точной подписью. Поскольку мне не нужно записывать какую-либо подпись (для этого потребовалось бы более сложное решение), этот вариант мне подходит. В основном он использовал enable_if_t.
struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };
// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T>
> : std::true_type {};
template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
> : std::true_type {};
template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
> : std::true_type {};
int main ()
{
constexpr bool has_sum_val = has_static_sum<Foo>::value;
constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;
constexpr bool has_calc_val = has_calc<Bar>::value;
constexpr bool not_has_calc_val = !has_calc<Foo>::value;
constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;
std::cout<< " has_sum_val " << has_sum_val << std::endl
<< " not_has_sum_val " << not_has_sum_val << std::endl
<< " has_calc_val " << has_calc_val << std::endl
<< " not_has_calc_val " << not_has_calc_val << std::endl
<< " has_calc_const_val " << has_calc_const_val << std::endl
<< "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}
Выход:
has_sum_val 1
not_has_sum_val 1
has_calc_val 1
not_has_calc_val 1
has_calc_const_val 1
not_has_calc_const_val 1
Хорошо. Вторая попытка Ничего страшного, если тебе это тоже не понравится, я ищу больше идей.
Статья Херба Саттера говорит о чертах. Таким образом, у вас может быть класс признаков, экземпляр которого по умолчанию имеет аварийное поведение, и для каждого класса, в котором существует ваша функция-член, класс признаков специализируется для вызова функции-члена. Я полагаю, что статья Херба упоминает технику, чтобы сделать это так, чтобы она не включала много копий и вставок.
Однако, как я уже сказал, возможно, вам не нужна дополнительная работа, связанная с "теговыми" классами, которые реализуют этот член. В этом случае я смотрю на третье решение....
Если вы используете глупость facebook, их макрос готов из коробки, чтобы помочь вам:
#include <folly/Traits.h>
namespace {
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace
void some_func() {
cout << "Does class Foo have a member int test() const? "
<< boolalpha << has_test_traits<Foo, int() const>::value;
}
Хотя детали реализации такие же, как и в предыдущем ответе, использовать библиотеку проще.
Основываясь на jrok"s ответ, я бы избежать использования вложенных классов и / или функции шаблона.
#include <type_traits>
#define CHECK_NESTED_FUNC(fName) \
template <typename, typename, typename = std::void_t<>> \
struct _has_##fName \
: public std::false_type {}; \
\
template <typename Class, typename Ret, typename... Args> \
struct _has_##fName<Class, Ret(Args...), \
std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
: public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
{}; \
\
template <typename Class, typename Signature> \
using has_##fName = _has_##fName<Class, Signature>;
#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value
Мы можем использовать вышеуказанные макросы, как показано ниже:
class Foo
{
public:
void Bar(int, const char *) {}
};
CHECK_NESTED_FUNC(Bar); // generate required metafunctions
int main()
{
using namespace std;
cout << boolalpha
<< HAS_NESTED_FUNC(Foo, Bar, void(int, const char *)) // prints true
<< endl;
return 0;
}
Предложения приветствуются.
Без поддержки C++11 (decltype
) это может сработать:
SSCCE
#include <iostream>
using namespace std;
struct A { void foo(void); };
struct Aa: public A { };
struct B { };
struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };
template<typename T>
struct FooFinder {
typedef char true_type[1];
typedef char false_type[2];
template<int>
struct TypeSink;
template<class U>
static true_type &match(U);
template<class U>
static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);
template<class U>
static false_type &test(...);
enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};
int main() {
cout << FooFinder<A>::value << endl;
cout << FooFinder<Aa>::value << endl;
cout << FooFinder<B>::value << endl;
cout << FooFinder<retA>::value << endl;
cout << FooFinder<argA>::value << endl;
cout << FooFinder<constA>::value << endl;
cout << FooFinder<varA>::value << endl;
}
Как это, надеюсь, работает
A
, Aa
а также B
являются ли эти вопросы, Aa
быть особенным, который наследует члена, которого мы ищем.
в FooFinder
true_type
а также false_type
являются заменами для соответствующих классов C++11. Также для понимания шаблонного метапрограммирования они раскрывают саму основу SFINAE-sizeof-трюка.
TypeSink
это шаблонная структура, которая используется позже, чтобы поглотить интегральный результат sizeof
оператор в создание шаблона для формирования типа.
match
Функция - это еще один вид шаблона SFINAE, который не имеет общего аналога. Следовательно, он может быть создан только в том случае, если тип его аргумента соответствует типу, для которого он был специализирован.
Оба test
функции вместе с объявлением enum, наконец, образуют центральный шаблон SFINAE. Существует универсальный, использующий многоточие, которое возвращает false_type
и аналог с более конкретными аргументами, чтобы иметь приоритет.
Чтобы иметь возможность создавать test
функция с аргументом шаблона T
, match
Функция должна быть создана, так как ее тип возврата требуется для создания экземпляра TypeSink
аргумент. Предостережение в том, что &U::foo
будучи обернутым в аргумент функции, на него не ссылаются из специализации аргумента шаблона, поэтому поиск унаследованного члена все же происходит.
Я думаю, что ответ, который вы ищете, здесь.
http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time
и чуть более заполненный пример здесь
Я использую технику для обнаружения присутствия поддерживающего оператора ostream << в рассматриваемом классе, а затем генерирую другой бит кода в зависимости.
Я не верил, что это было возможно, прежде чем найти связанное решение, но это очень изящный прием. Потратьте время на понимание кода, и это того стоит.
штифтик