Проверьте, имеет ли class функцию-член определенной подписи

Я прошу шаблонный трюк, чтобы определить, имеет ли class определенную функцию-член определенной подписи.

Проблема аналогична приведенной здесь: http://www.gotw.ca/gotw/071.htm, но не то же самое: в статье книги Саттера он ответил на вопрос, что class C ДОЛЖЕН ПРЕДОСТАВИТЬ функцию-член с конкретная подпись, иначе программа не будет компилироваться. В моей проблеме мне нужно что-то сделать, если class имеет эту функцию, иначе «что-то еще».

Аналогичная проблема была решена с помощью boost :: serialization, но мне не нравится решение, которое они приняли: функция шаблона, которая по умолчанию запускает бесплатную функцию (которую вы должны определить) с определенной сигнатурой, если вы не определите определенную функцию-член ( в случае «сериализации», который принимает 2 параметра данного типа) с определенной сигнатурой, иначе произойдет ошибка компиляции. То есть для реализации как интрузивной, так и неинтрузивной сериализации.

Мне не нравится это решение по двум причинам:

  1. Чтобы быть неинтрузивным, вы должны переопределить глобальную функцию «сериализации», которая находится в пространстве имен boost :: serialization, поэтому у вас есть В КОДЕ КЛАССА, чтобы открыть пространство имен и пространственную сериализацию пространства имен!
  2. Стек для разрешения этого беспорядка – от 10 до 12 вызовов функций.

Мне нужно определить настраиваемое поведение для classов, у которых нет этой функции-члена, и мои сущности находятся внутри разных пространств имен (и я не хочу переопределять глобальную функцию, определенную в одном пространстве имен, пока я в другом)

Можете ли вы дать мне подсказку, чтобы решить эту загадку?

13 Solutions collect form web for “Проверьте, имеет ли class функцию-член определенной подписи”

Я не уверен, правильно ли я вас понимаю, но вы можете использовать SFINAE для обнаружения присутствия функции во время компиляции. Пример из моего кода (тесты, если class имеет функцию-член size_t used_memory () const).

template struct HasUsedMemoryMethod { template struct SFINAE {}; template static char Test(SFINAE*); template static int Test(...); static const bool Has = sizeof(Test(0)) == sizeof(char); }; template void ReportMemUsage(const TMap& m, std::true_type) { // We may call used_memory() on m here. } template void ReportMemUsage(const TMap&, std::false_type) { } template void ReportMemUsage(const TMap& m) { ReportMemUsage(m, std::integral_constant::Has>()); } 

Вот возможная реализация, основанная на возможностях C ++ 11. Он правильно определяет функцию, даже если она унаследована (в отличие от решения в принятом ответе, как отмечает Майк Кингхан в своем ответе ).

Функция, которую этот fragment проверяет, называется serialize :

 #include  // 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 struct has_serialize { static_assert( std::integral_constant::value, "Second template parameter needs to be of function type."); }; // specialization that does the checking template struct has_serialize { private: template static constexpr auto check(T*) -> typename std::is_same< decltype( std::declval().serialize( std::declval()... ) ), Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >::type; // attempt to call it and see if the return type is correct template static constexpr std::false_type check(...); typedef decltype(check(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::value; // will print 1 

Принятый ответ на этот вопрос интроспекции членской функции compiletime, хотя и является поистине популярным, имеет компромат, который можно наблюдать в следующей программе:

 #include  #include  #include  /* Here we apply the accepted answer's technique to probe for the the existence of `ET::operator*() const` */ template struct has_const_reference_op { template struct SFINAE {}; template static char Test(SFINAE*); template static int Test(...); static const bool value = sizeof(Test(0)) == sizeof(char); }; using namespace std; /* Here we test the `std::` smart pointer templates, including the deprecated `auto_ptr`, 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,int &>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value < < endl; return 0; } 

Построенный с GCC 4.6.3, программные выходы 110 - сообщают нам, что T = std::shared_ptr не предоставляет int & T::operator*() const .

Если вы уже не разумны в этом вопросе, то взгляд на определение std::shared_ptr в заголовке пролить свет. В этой реализации std::shared_ptr выводится из базового classа, из которого он наследует operator*() const . Таким образом, экземпляр шаблона SFINAE который представляет собой «поиск» для U = std::shared_ptr , не произойдет, поскольку std::shared_ptr не имеет operator*() в его собственное право и создание шаблона не «наследуют».

Эта загвоздка не влияет на хорошо известный подход SFINAE, используя «Theofofofof () Trick», для обнаружения только того, имеет ли T некоторую функцию-член mf (см., Например, этот ответ и комментарии). Но установление того, что T::mf существует, часто (обычно?) Недостаточно хорош: вам также может потребоваться установить, что он имеет желаемую подпись. Вот где проиллюстрирована техническая оценка. Указанный вариант желаемой сигнатуры вписан в параметр типа шаблона, который должен быть удовлетворен &T::mf для зонда SFINAE для успеха. Но эта технология создания шаблона дает неправильный ответ, когда T::mf наследуется.

Безопасный метод SFINAE для интроспекции T::mf для compiletime должен избегать использования &T::mf в аргументе шаблона для создания экземпляра типа, от которого зависит разрешение шаблона функции SFINAE. Вместо этого разрешение функции шаблона SFINAE может зависеть только от точно соответствующих деклараций типа, используемых в качестве типов аргументов перегруженной функции зонда SFINAE.

В качестве ответа на вопрос, который соблюдает это ограничение, я проиллюстрирую для компиляции определения ET::operator*() const для произвольных T и E Эта же модель будет применяться mutatis mutandis для исследования любой другой сигнатуры метода участника.

 #include  /*! The template `has_const_reference_op` exports a boolean constant `value that is true iff `T` provides `ET::operator*() const` */ template< typename T, typename E> struct has_const_reference_op { /* SFINAE operator-has-correct-sig :) */ template static std::true_type test(E (A::*)() const) { return std::true_type(); } /* SFINAE operator-exists :) */ template  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 static std::false_type test(...) { return std::false_type(); } /* This will be either `std::true_type` or `std::false_type` */ typedef decltype(test(0,0)) type; static const bool value = type::value; /* Which is it? */ }; 

В этом решении перегруженный тест функции SFINAE test() «вызывается рекурсивно». (Конечно, он вообще не вызывается, он просто имеет возвращаемые типы гипотетических вызовов, разрешенных компилятором.)

Нам нужно исследовать хотя бы одну и не более двух точек информации:

  • Существует ли T::operator*() вообще? Если нет, мы закончили.
  • Учитывая, что существует T::operator*() , является ли его сигнатура ET::operator*() const ?

Мы получаем ответы, оценивая тип возврата одного вызова для test(0,0) . Это делается путем:

  typedef decltype(test(0,0)) type; 

Этот вызов может быть разрешен /* SFINAE operator-exists :) */ overload of test() , или он может решить /* SFINAE game over :( */ overload. Он не может разрешить /* SFINAE operator-has-correct-sig :) */ overload, потому что тот ожидает только один аргумент, и мы проходим два.

Почему мы проходим два? Просто заставить разрешение исключить /* 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*)) - тип возвращаемого значения другого call to 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) вызова test(0,0) или не выбирает /* SFINAE operator-has-correct-sig :) */ для test(&A::operator*) вызова 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 struct ee_ref { E & operator*() { return *_pe; } E & foo() const { return *_pe; } E * _pe; }; // To test struct sub_ee_ref : ee_ref{}; using namespace std; #include  #include  #include  int main(void) { cout < < "Expect Yes" << endl; cout << has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op::iterator,int &>::value; cout < < has_const_reference_op::const_iterator, int const &>::value; cout < < has_const_reference_op::value; cout < < has_const_reference_op::value < < endl; cout << "Expect No" << endl; cout << has_const_reference_op::value; cout < < has_const_reference_op,char &>::value; cout < < has_const_reference_op,int const &>::value; cout < < has_const_reference_op,int>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op::value; cout < < has_const_reference_op::value < < endl; return 0; } 

Есть ли новые недостатки в этой идее? Можно ли сделать его более универсальным, не теряя при этом недостатка, который он избегает?

Этого должно быть достаточно, если вы знаете имя функции-члена, которую вы ожидаете. (В этом случае функция bla не может создать экземпляр, если нет какой-либо функции-члена (писать все, что работает в любом случае, является жестким, потому что отсутствует частичная специализация функции. Возможно, вам придется использовать шаблоны classов). Кроме того, разрешающая структура (которая похож на enable_if) также может быть запрограммирован на тип функции, которую вы хотите иметь в качестве члена.

 template  struct enable { typedef T type; }; template  typename enable::type bla (T&); struct A { void i(); }; struct B { int i(); }; int main() { A a; B b; bla(b); bla(a); } 

Вот некоторые fragmentы использования: * Кишки для всего этого более далеки

Проверьте член x в данном classе. Может быть var, func, class, union или enum:

 CREATE_MEMBER_CHECK(x); bool has_x = has_member_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::value; 

Проверьте переменную-член x :

 CREATE_MEMBER_VAR_CHECK(x); bool has_var_x = has_member_var_x::value; 

Проверьте class участника x :

 CREATE_MEMBER_CLASS_CHECK(x); bool has_class_x = has_member_class_x::value; 

Проверьте членский член x :

 CREATE_MEMBER_UNION_CHECK(x); bool has_union_x = has_member_union_x::value; 

Проверьте член enum x :

 CREATE_MEMBER_ENUM_CHECK(x); bool has_enum_x = has_member_enum_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::value; 

ИЛИ

 CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above. bool has_any_func_x = has_member_func_x::value; 

Детали и kernel:

 /* - 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  struct ambiguate : public Args... {}; //Non-variadic version of the line above. //template  struct ambiguate : public A, public B {}; template struct got_type : std::false_type {}; template struct got_type : std::true_type { typedef A type; }; template struct sig_check : std::true_type {}; template struct has_member { template static char ((&f(decltype(&C::value))))[1]; template static char ((&f(...)))[2]; //Make sure the member name is consistently spelled the same. static_assert( (sizeof(f(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(0)) == 2; }; 

Макросы (El Diablo!):

CREATE_MEMBER_CHECK:

 //Check for any member with given name, whether var, func, class, union, enum. #define CREATE_MEMBER_CHECK(member) \ \ template \ struct Alias_##member; \ \ template \ struct Alias_##member < \ T, std::integral_constant::value> \ > { static const decltype(&T::member) value; }; \ \ struct AmbiguitySeed_##member { char member; }; \ \ template \ struct has_member_##member { \ static const bool value \ = has_member< \ Alias_##member> \ , Alias_##member \ >::value \ ; \ } 

CREATE_MEMBER_VAR_CHECK:

 //Check for member variable with given name. #define CREATE_MEMBER_VAR_CHECK(var_name) \ \ template \ struct has_member_var_##var_name : std::false_type {}; \ \ template \ struct has_member_var_##var_name< \ T \ , std::integral_constant< \ bool \ , !std::is_member_function_pointer::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 \ struct has_member_func_##templ_postfix : std::false_type {}; \ \ template \ struct has_member_func_##templ_postfix< \ T, std::integral_constant< \ bool \ , sig_check::value \ > \ > : std::true_type {} 

CREATE_MEMBER_CLASS_CHECK:

 //Check for member class with given name. #define CREATE_MEMBER_CLASS_CHECK(class_name) \ \ template \ struct has_member_class_##class_name : std::false_type {}; \ \ template \ struct has_member_class_##class_name< \ T \ , std::integral_constant< \ bool \ , std::is_class< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_UNION_CHECK:

 //Check for member union with given name. #define CREATE_MEMBER_UNION_CHECK(union_name) \ \ template \ struct has_member_union_##union_name : std::false_type {}; \ \ template \ struct has_member_union_##union_name< \ T \ , std::integral_constant< \ bool \ , std::is_union< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_ENUM_CHECK:

 //Check for member enum with given name. #define CREATE_MEMBER_ENUM_CHECK(enum_name) \ \ template \ struct has_member_enum_##enum_name : std::false_type {}; \ \ template \ struct has_member_enum_##enum_name< \ T \ , std::integral_constant< \ bool \ , std::is_enum< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_FUNC_CHECK:

 //Check for function with given name, any signature. #define CREATE_MEMBER_FUNC_CHECK(func) \ template \ struct has_member_func_##func { \ static const bool value \ = has_member_##func::value \ && !has_member_var_##func::value \ && !has_member_class_##func::value \ && !has_member_union_##func::value \ && !has_member_enum_##func::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) 

Вы можете использовать std :: is_member_function_pointer

 class A { public: void foo() {}; } bool test = std::is_member_function_pointer::value; 

Сам столкнулся с той же проблемой, и нашел предлагаемые решения здесь очень интересными … но требовал решения:

  1. Обнаруживает и унаследованные функции;
  2. Совместим с компиляторами, отличными от C ++ 11 (так что нет decltype)

Нашел другой stream, предлагающий нечто подобное, основанное на обсуждении BOOST . Вот обобщение предлагаемого решения как двух деклараций макросов для classа признаков, следуя модели classов boost :: has_ ​​* .

 #include  #include  /// 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::value \ > \ class has_func_ ## func_name; \ template \ class has_func_ ## func_name \ {public: \ BOOST_STATIC_CONSTANT( bool, value = false ); \ typedef boost::false_type type; \ }; \ template \ class has_func_ ## func_name \ { 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  class Helper{}; \ template  \ 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(0) ) ) \ ); \ typedef ::boost::integral_constant 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__ 

Эти macros расширяются до classа признаков со следующим прототипом:

 template 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 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  // Here is a function that is active for types // declaring the good member function template inline typename boost::enable_if< ns_0::has_func_foo >::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 inline typename boost::disable_if< ns_0::has_func_foo >::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) } 

Вот более простой ответ на ответ Майка Кингхана. Это обнаружит унаследованные методы. Он также будет проверять точную подпись (в отличие от подхода jrok, который позволяет преобразовывать аргументы).

 template  class HasGreetMethod { template  static std::true_type testSignature(void (T::*)(const char*) const); template  static decltype(testSignature(&T::greet)) test(std::nullptr_t); template  static std::false_type test(...); public: using type = decltype(test(nullptr)); static const bool value = type::value; }; struct A { void greet(const char* name) const; }; struct Derived : A { }; static_assert(HasGreetMethod::value, ""); 

Пример Runnable

Для этого нам нужно будет использовать:

  1. Перегрузка шаблона функции с различными типами возврата в зависимости от того, доступен ли этот метод
  2. В соответствии с type_traits заголовке type_traits мы хотим вернуть true_type или false_type из наших перегрузок
  3. true_type перегрузку true_type ожидающую int и перегрузку false_type ожидающую использования Variadic Parameters: «Самый низкий приоритет преобразования многоточия при разрешении перегрузки»
  4. При определении спецификации шаблона для функции true_type мы будем использовать declval и decltype позволяющие нам обнаруживать функцию независимо от различий типа возвращаемого типа или перегрузок между методами

Вы можете увидеть живой пример этого здесь , но я объясню это ниже:

Я хочу проверить наличие функции с именем test которая принимает тип convertible из int , тогда мне нужно будет объявить эти две функции:

 template ().test(declval))> static true_type hasTest(int); template  static false_type hasTest(...); 
  • decltype(hasTest(0))::value is true (Обратите внимание: нет необходимости создавать специальные функции для работы с перегрузкой void a::test(int) , принимается void a::test(int) )
  • decltype(hasTest(0))::value is true (потому что int преобразуется в double int b::test(double) принимается независимо от типа возврата)
  • decltype(hasTest(0))::value is false ( c не имеет метода с именем test который принимает тип конвертируемого из int потому что это не принимается)

Это решение имеет два недостатка:

  1. Требуется объявление каждого метода для пары функций
  2. Создает загрязнение пространства имен, особенно если мы хотим проверить похожие имена, например, какую бы мы назвали функцию, которая хотела бы протестировать метод test() ?

Поэтому важно, чтобы эти функции были объявлены в пространстве имен деталей, или в идеале, если они предназначены только для использования с classом, они должны быть объявлены частным образом этим classом. Для этого я написал макрос, который поможет вам абстрагировать эту информацию:

 #define FOO(FUNCTION, DEFINE) template ().FUNCTION)> static true_type __ ## DEFINE(int); \ template  static false_type __ ## DEFINE(...); \ template  using DEFINE = decltype(__ ## DEFINE(0)); 

Вы можете использовать это как:

 namespace details { FOO(test(declval()), test_int) FOO(test(), test_void) } 

Впоследствии details::test_int::value вызове details::test_int::value или details::test_void::value даст true или false для целей встроенного кода или метапрограммирования.

Чтобы быть неинтрузивным, вы можете также serialize в пространстве имен сериализуемого classа или classа архива, благодаря поиску Koenig . Дополнительные сведения см. В разделе Пространства имен для бесплатных переопределений функций . 🙂

Открытие любого пространства имен для реализации свободной функции просто неверно. (например, вы не должны открывать пространство имен std для реализации swap для ваших собственных типов, но вместо этого следует использовать поиск Koenig.)

Хорошо. Вторая попытка. Это нормально, если вам это не нравится, я ищу больше идей.

Статья Херба Саттера рассказывает о чертах. Таким образом, у вас может быть class признаков, у экземпляра по умолчанию есть резервное поведение, а для каждого classа, где существует ваша функция-член, class признаков специализирован для вызова функции-члена. Я считаю, что в статье Херба упоминается техника для этого, чтобы она не включала много копирования и вставки.

Как я уже сказал, возможно, вам не нужна дополнительная работа, связанная с classами «tagging», которые реализуют этот член. В этом случае я смотрю на третье решение ….

Я считаю, что ответ, который вы ищете, здесь.

http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time

и немного более заполненный пример здесь

http://pastie.org/298994

Я использую эту технику, чтобы обнаружить наличие поддерживающего операционного оператора < < на рассматриваемом classе, а затем сгенерировать другой бит кода в зависимости.

Я не думал, что это возможно, прежде чем найти связанное решение, но это очень аккуратный трюк. Потратьте время на понимание кода, и это очень стоит.

штифтик

Без поддержки C ++ 11 ( decltype ) это может работать:

SSCCE

 #include  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 struct FooFinder { typedef char true_type[1]; typedef char false_type[2]; template struct TypeSink; template static true_type &match(U); template static true_type &test(TypeSink( &U::foo ) )> *); template static false_type &test(...); enum { value = (sizeof(test(0, 0)) == sizeof(true_type)) }; }; int main() { cout < < FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; } 

Как он, надеюсь, работает

A , Aa и B - рассматриваемые кланы, Aa - особый, который наследует члена, которого мы ищем.

В FooFinder true_type и false_type являются заменами соответствующих classов C ++ 11. Также для понимания мета-программирования шаблонов они раскрывают саму основу SFINAE-sizeof-trick.

TypeSink - это структура шаблонов, которая используется позже, чтобы потопить интегральный результат оператора sizeof в экземпляр шаблона для формирования типа.

Функция match - это другой шаблон типа SFINAE, который остается без общего экземпляра. Поэтому он может быть создан только в том случае, если тип его аргумента соответствует типу, для которого он был специализирован.

Both the test functions together with the enum declaration finally form the central SFINAE pattern. There is a generic one using an ellipsis that returns the false_type and a counterpart with more specific arguments to take precedence.

To be able to instantiate the test function with a template argument of T , the match function must be instantiated, as its return type is required to instantiate the TypeSink argument. The caveat is that &U::foo , being wrapped in a function argument, is not referred to from within a template argument specialization, so inherited member lookup still takes place.

  • объявлять функцию шаблона шаблона classа шаблона
  • Метапрограммирование: отказ определения функции Определяет отдельную функцию
  • Инициализация статического члена в шаблоне classа
  • Статический polymorphism C ++ (CRTP) и использование typedefs из производных classов
  • постоянные ссылки с typedef и шаблонами в c ++
  • Если я хочу специализироваться только на одном методе в шаблоне, как мне это сделать?
  • Когда следует использовать Observer и Observable
  • Делают ли c ++ шаблоны медленными?
  • Расширение шаблона шаблонов Variadic
  • Почему контейнеры C ++ не допускают неполные типы?
  • Сведения о создании шаблона компиляторов GCC и MS
  • Давайте будем гением компьютера.