Можно ли написать шаблон для проверки существования функции?

413

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

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

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".

Теги:
templates
template-meta-programming
sfinae

23 ответа

275
Лучший ответ

Да, с 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. Я не знаю, переносимо ли это на другие платформы, работающие на разных компиляторах.

  • 1
    Ах, ты побил меня к этому! Это очень хороший трюк (подтвердил работу с GCC 4.1 на Mac OSX 10.5)
  • 16
    Хотя я использовал следующее для 'one' и 'two': typedef char Small; класс Big {char dummy [2];}, чтобы не допустить двусмысленности относительно размера переменной, зависящей от платформы.
Показать ещё 17 комментариев
249

Этот вопрос старый, но с С++ 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 в 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.

  • 3
    Преимущество decltype сравнению с sizeof также заключается в том, что временное правило не вводится специально созданными правилами для вызовов функций (поэтому вам не нужно иметь права доступа к деструктору возвращаемого типа и вы не будете вызывать неявную реализацию, если возвращаете тип является экземпляром шаблона класса).
  • 4
    Microsoft еще не внедрила Expression SFINAE в свой компилятор C ++. Просто подумайте, что я могу помочь сэкономить время некоторых людей, потому что я был озадачен, почему это не работает для меня. Хорошее решение, хотя, не могу дождаться, чтобы использовать его в Visual Studio!
Показать ещё 8 комментариев
155

С++ позволяет использовать SFINAE (обратите внимание, что с возможностями С++ 11 это проще, потому что он поддерживает расширенный SFINAE на почти произвольные выражения - ниже было создано для работы с обычными компиляторами С++ 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 в этой ветке if. так как компилятор будет проверять достоверность в обеих ветвях, что не удастся для случаев, когда функция не существует. Один из способов - снова использовать SFINAE (enable_if может быть получен от повышения):

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 в качестве типа указателя функции-члена!).

  • 1
    что за чк используется в макросе?
  • 1
    @lurscher - это перегруженная функция, где первая перегрузка возвращает ссылку на массив символов размера 1, а другой - размера 2. При использовании SFINAE эта маска маскируется.
Показать ещё 16 комментариев
54

Хотя этот вопрос два года, я осмелюсь добавить свой ответ. Надеемся, что она будет разъяснено предыдущий, бесспорно отличный, решение. Я взял очень полезные ответы Никола Бонелли и Йоханнеса Шауба и объединил их в решение IMHO, более читаемое, понятное и не требующее расширения 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. Кредит в основном касается Никола Бонелли и Йоханнеса Шауба, поэтому дайте им пройти голосование, если мой ответ поможет вам:)

  • 1
    Просто интересно, делает ли это что-то, чего не делает приведенное ниже решение Конрада Рудольфа?
  • 3
    @AlastairIrvine, это решение скрывает всю логику внутри, Konrad's возлагает некоторую нагрузку на пользователя. Несмотря на то, что решение Конрада короткое и гораздо более читаемое, требуется отдельная специализация шаблонов для каждого класса, имеющего toString . Если вы пишете универсальную библиотеку, которая хочет работать с любым классом (подумайте о чем-то вроде boost), то требование пользователя определить дополнительные специализации некоторых непонятных шаблонов может быть неприемлемым. Иногда желательно написать очень сложный код, чтобы сделать публичный интерфейс настолько простым, насколько это возможно.
43

Инструментарий обнаружения

N4502 предлагает обнаружение для включения в стандартную библиотеку С++ 17, которая может решить проблему несколько элегантным образом. Более того, его просто приняли в основу библиотеки TS v2. Он вводит некоторые метафайлы, в том числе std::is_detected которые могут использоваться для легкого написания 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>;

Обратите внимание, что приведенный выше пример не проверен. Инструментарий обнаружения недоступен в стандартных библиотеках, но предложение содержит полную реализацию, которую вы можете легко скопировать, если вам это действительно нужно. Он отлично if constexpr функцией С++ 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 by has_member_function_toString<std::string T::*()>::value.

28

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

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;
}
  • 3
    +1: черты всегда были чистым способом решения проблем.
  • 5
    Вы должны предпочесть enum для признаков вместо статических констант: «Членами статической константы являются lvalues, что вынуждает компилятор создавать и выделять определение для статического члена. В результате вычисления больше не ограничиваются чистым» временем компиляции "эффект"
Показать ещё 13 комментариев
24

Простое решение для С++ 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";
}
  • 2
    Это просто и элегантно, но, строго говоря, не отвечает на вопрос OP: вы не позволяете вызывающей стороне проверять существование функции, вы всегда предоставляете ее. Но все равно приятно.
  • 0
    @AdrianW, хорошая мысль. Я обновил свой ответ. Я не проверял это хотя
Показать ещё 1 комментарий
19

Ну, у этого вопроса уже есть длинный список ответов, но я хотел бы подчеркнуть комментарий от Morwenn: есть предложение для С++ 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 {};

то есть переменная часть, в которой вы указываете, что вы ищете (тип, тип члена, функция, функция-член и т.д.). В случае OP:

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 следуют другой интерпретации стандарта С++ 11, из-за чего void_t не работает должным образом. Якк уже обеспечил обход: используйте следующее определение void_t (void_t в списке параметров, но не как тип возврата):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.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
  • 0
    Можно ли расширить его для обнаружения функций, не являющихся членами?
  • 0
    Да, конечно. Посмотрите внимательно на примеры: вы в основном предоставляете выражение и проверяете, действительно ли оно. Ничто не требует, чтобы это выражение было только о вызове функции-члена.
Показать ещё 1 комментарий
8

Это решение С++ 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 {};

Trait 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 года довольно сложно.

Обратите внимание, что в С++ 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";
  });
}

который является условной ветвью встроенной компиляции, которая злоупотребляет множеством функций С++. Выполнение этого, вероятно, не стоит того, так как преимущество (встроенного кода) не стоит затрат (рядом с кем-то, кто не понимает, как он работает), но существование этого выше решения может представлять интерес.

  • 0
    Это обрабатывает частные случаи?
  • 0
    @ tower120 Мне бы пришлось поэкспериментировать: как шаблоны взаимодействуют с private / public / protected, мне немного неясно. Однако не имеет значения, где вы вызываете has_to_string .
Показать ещё 4 комментария
8

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

Проверить член 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;

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

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Проверить членство enum 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;
};

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

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)
  • 1
    Есть ли у вас какие-либо идеи, почему, если мы sig_check<func_sig, &T::func_name> на бесплатную проверку функции: sig_check<func_sig, &func_name> не сможет sig_check<func_sig, &func_name> с «необъявленным идентификатором» с упоминанием имени функции, которую мы хотим проверить? потому что я ожидал бы, что SFINAE сделает это НЕ ошибкой, он делает это только для членов, почему не для свободных функций?
  • 0
    Я предполагаю, что это как-то связано с тем, что свободная функция не является классом или структурой. Этот метод определения присутствия члена действительно основан на механизме множественного наследования в C ++, что вызывает неоднозначность между классом-заглушкой, который существует только с целью размещения члена, который вы проверяете, по сравнению с классом, который вы фактически проверяете для члена в. Это интересный вопрос, хотя, не думал об этом. Вы можете проверить другие методы проверки членов C ++ 11/14, я видел некоторые умные вещи в новом стандарте.
Показать ещё 1 комментарий
6

Я написал ответ на этот вопрос в другом потоке, который (в отличие от вышеприведенных решений) также проверяет унаследованные функции-члены:

SFINAE для проверки наследуемых функций-членов

Вот пример из этого решения:

Пример1:

Мы проверяем участника со следующей подписью: 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 не должен быть по умолчанию конструктивным или удовлетворять любой специальной концепции. Этот метод также работает с членами шаблона.

Я с нетерпением жду мнения относительно этого.

6

Стандартное решение на С++, представленное здесь через 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.

  • 0
    Хм, разработав свою собственную версию с использованием этих сообщений, я обнаружил, что у этой идеи есть некоторые другие недостатки, поэтому я снова удалил код из своего ответа. Во-первых, все функции должны быть общедоступными в целевом типе. Таким образом, вы не можете проверить функцию "f" в этом: struct g { void f(); private: void f(int); }; потому что одна из функций является закрытой (это потому, что код using g::f; что приводит к сбою, если любой f недоступен).
6

Теперь это была приятная маленькая головоломка - отличный вопрос!

Здесь альтернатива Nicola Bonelli solution, которая не зависит от нестандартного оператора typeof.

К сожалению, он не работает на GCC (MinGW) 3.4.5 или Digital Mars 8.42n, но он работает во всех версиях MSVC (включая VC6) и на Comeau С++.

Более длинный блок комментариев содержит сведения о том, как он работает (или должен работать). Как говорится, я не уверен, какое поведение соответствует стандартам - я бы приветствовал комментарии по этому поводу.


update - 7 ноября 2008:

Похоже, что, хотя этот код синтаксически корректен, поведение, которое демонстрирует MSVC и Comeau С++, не соответствует стандарту (благодаря Leon Timmermans и litb для указания меня в правильном направлении). В стандарте С++ 03 говорится следующее:

14.6.2 Зависимые имена [temp.dep]

Пункт 3

В определении шаблона класса или член шаблона класса, если базовый класс шаблона класса зависит от параметра шаблона, область базового класса не рассматривается при поиске неквалифицированного имени либо в точке определения класса или члена или во время создание шаблона класса или член.

Итак, похоже, что когда MSVC или Comeau рассматривают функцию члена toString() T, выполняющую поиск имени на сайте вызова в doToString() при создании экземпляра шаблона, это неверно (хотя это фактически поведение, которое я искал в этом случае).

Поведение GCC и Digital Mars выглядит корректно - в обоих случаях функция non-member 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 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;
}
  • 1
    Нет, он не соответствует стандартам, хотя я думаю, что он будет работать в GCC, если вы включите опцию -fpermissive.
  • 0
    Я знаю, что комментарии не дают много места, но не могли бы вы указать информацию о том, почему они не соответствуют стандартам? (Я не спорю - мне любопытно)
Показать ещё 5 комментариев
5

В MSVC есть слова __if_exists и __if_not_exists (Doc). Вместе с подходом типа SFINAE Nicola я мог бы создать проверку для GCC и MSVC, которые искали OP.

Обновление: Источник можно найти Здесь

  • 0
    Большое спасибо за этот ответ! Вы сделали мой день намного проще.
4

Я изменил решение, представленное в https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence, чтобы сделать его более общим. Кроме того, поскольку он не использует какие-либо новые возможности С++ 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); \
   }

Мы можем использовать указанные выше макросы для выполнения проверок для 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;
}
  • 0
    Вы можете улучшить это, чтобы поддерживать функции-члены с аргументами шаблона. Измените шаблон <typename T> на шаблон <typename T, typename ... Args>, затем вы можете использовать «Args ...» в макросе elipsis для создания структуры проверки с переменными аргументами шаблона. например. Определить метод "void onNext (const T &)" HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ... template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
3

Общий шаблон, который можно использовать для проверки того, поддерживается ли какая-либо "функция" типом:

#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;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4

  • 0
    Есть ли способ has_foo в вызов шаблона is_supported . Я хотел бы вызвать что-то вроде: std::cout << is_supported<magic.foo(), struct1>::value << std::endl; , Причина этого в том, что я хочу определить has_foo для каждой сигнатуры другой функции, которую я хочу проверить, прежде чем я смогу проверить функцию?
3

Странно никому не предлагали следующий приятный трюк, который я видел однажды на этом самом сайте:

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, но не уверен, что он стандартный.

2

Пример использования 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");
2

Здесь много ответов, но мне не удалось найти версию, которая выполняет упорядочивание реального порядка, не используя ни одну из новых функций С++ (только с использованием функций С++ 98).
Примечание. Эта версия протестирована и работает с vС++ 2013, g++ 5.2.0 и компилятором onlline.

Итак, я придумал версию, которая использует только 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,"");

Живая демонстрация (с расширенной проверкой типа возвращаемого значения и обходной способ обхода vС++ 2010): http://cpp.sh/5b2vs

Нет источника, поскольку я сам придумал его.

При запуске демонстрации Live в компиляторе g++ обратите внимание, что размеры массива 0 равны, что означает, что используемый static_assert не приведет к ошибке компилятора, даже если он не работает.
Обычно используется обход, чтобы заменить "typedef" в макросе "extern".

  • 0
    declval это C ++ 98?
  • 0
    Нет, но я объявляю это сам, и он не использует rvalue (посмотрите на верхнюю часть моего кода). Или вы можете просто убедить себя и попробовать живую демонстрацию в режиме c ++ 98. PS: static_assert тоже не c ++ 98, но есть обходные пути (живая демонстрация)
Показать ещё 4 комментария
1

Вы можете пропустить все метапрограммирование в С++ 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);
}

Вы также можете создать функцию непосредственно из lambdas:

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()
);
  • 0
    Насколько легко это написать, чтобы не зависеть от fit или какой-либо другой библиотеки, кроме стандартной?
1

Вот моя версия, которая обрабатывает все возможные перегрузки функции-члена с произвольной arity, включая функции-члены шаблона, возможно с аргументами по умолчанию. Он выделяет три взаимоисключающих сценария при вызове функции-члена для некоторого типа класса с заданными типами аргументов: (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;
}

Вот код, написанный на С++ 11, однако вы можете легко перенести его (с небольшими изменениями) в не-С++ 11 с расширениями типаof (например, 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

1

Как насчет этого решения?

#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 { };
  • 0
    Сбой, если toString перегружен, так как &U::toString является неоднозначным.
  • 0
    @ Якк Я думаю, актеры могут решить эту проблему.
0

Вот пример рабочего кода.

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
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню