Шаблонная проверка на существование функции-члена класса?

499

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

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

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

Энди
источник
6
Конечно, само собой разумеется, что шаблонный ответ (ы) ниже работает только с информацией времени компиляции, т. Е. T должен иметь toString. Если вы передадите подкласс T, который определит toString, а T - нет , вам скажут, что toString не определено.
Алиса Перселл,
Возможный дубликат Как проверить, существует ли имя класса (переменная или функция) в классе, с указанием типа или без него? , поскольку он охватывает более широкую проблему с C ++ 03 до C ++ 1y.
iammilind

Ответы:

319

Да, с SFINAE вы можете проверить, предоставляет ли данный класс определенный метод. Вот рабочий код:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

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

Никола Бонелли
источник
18
Хотя я использовал следующее для 'one' и 'two': typedef char Small; класс Big {char dummy [2];}, чтобы не допустить двусмысленности относительно размера переменной, зависящей от платформы.
user23167
6
Я сомневаюсь, что на земле существует платформа с sizeof (char) == sizeof (long)
Никола Бонелли
17
Я не совсем уверен, но я не думаю, что это портативно. typeof - это расширение GCC, оно не будет работать на других компиляторах.
Леон Тиммерманс
56
typeof не нужен - char [sizeof (& C :: helloworld)] также работает. И чтобы избежать sizeof (long) == sizeof (char), используйте struct {char [2]} ;. Он должен иметь размер> = 2
MSalters
57
Trivial, но мне потребовалось некоторое время , чтобы понять: заменить typeofна decltypeпри использовании C ++ 0x , например, с помощью -std = C ++ 0x.
ПЧР
265

Этот вопрос старый, но с C ++ 11 мы получили новый способ проверки существования функций (или существования любого не типового члена, на самом деле), снова полагаясь на SFINAE:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

Теперь о некоторых объяснениях. Во-первых, я использую выражение SFINAE чтобы исключить serialize(_imp)функции из разрешения перегрузки, если первое выражение внутриdecltype недопустимо (иначе функция не существует).

void()Используется , чтобы сделать тип возвращаемого значения всех этих функцийvoid .

0Аргумент используется предпочитать os << objперегрузки , если оба доступны (дословный0 имеет типа intи как таковую первую перегрузка лучше подходит).


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

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Живой пример.

И по объяснениям. Во-первых, sfinae_trueэто вспомогательный тип, и он в основном равен написанию decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Преимущество просто в том, что он короче.
Далее, struct has_stream : decltype(...)наследуется от одного std::true_typeили std::false_typeв конце, в зависимости от того, не прошла ли decltypeрегистрация test_stream.
Наконец, std::declvalдает вам «значение» любого типа, который вы передаете, без необходимости знать, как вы можете его построить. Обратите внимание, что это возможно только в недооцененном контексте, например decltype,sizeof и другие.


Обратите внимание, что decltypeэто не обязательно необходимо, поскольку sizeof(и все неоцененные контексты) получили это улучшение. Просто это decltypeуже дает тип и, как таковое, просто чище. Вот sizeofверсия одной из перегрузок:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

Параметры intи longпо-прежнему присутствуют по той же причине. Указатель массива используется для предоставления контекста, в котором sizeofможно использовать.

Xeo
источник
4
Преимущество decltypeover sizeofтакже заключается в том, что временное не вводится специально созданными правилами для вызовов функций (поэтому вам не нужно иметь права доступа к деструктору возвращаемого типа и не будет вызываться неявная реализация, если возвращаемый тип создание шаблона класса).
Йоханнес Шауб -
5
Microsoft еще не внедрила Expression SFINAE в свой компилятор C ++. Просто подумайте, что я могу помочь сэкономить время некоторых людей, потому что я был озадачен, почему это не работает для меня. Хорошее решение, хотя, не могу дождаться, чтобы использовать его в Visual Studio!
Джонатан
3
Ваш первый пример ссылки не работает
NathanOliver
1
Надо сказать, что он static_assert(has_stream<X, char>() == true, "fail X");будет компилироваться, а не утверждаться, потому что char может быть преобразован в int, поэтому, если такое поведение нежелательно и нужно, чтобы все типы аргументов совпадали, я не знаю, как этого можно достичь?
Габриэль
4
Если вы так же озадачены, как и я, с двумя аргументами для decltype: decltype действительно принимает только один; запятая здесь оператор. См. Stackoverflow.com/questions/16044514/…
Андре
159

C ++ позволяет использовать SFINAE для этого (обратите внимание, что с функциями C ++ 11 это проще, потому что он поддерживает расширенный SFINAE для почти произвольных выражений - ниже было создано для работы с обычными компиляторами C ++ 03):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

вышеупомянутый шаблон и макрос пытается создать экземпляр шаблона, давая ему тип указателя на функцию-член и фактический указатель на функцию-член. Если типы не подходят, SFINAE приводит к игнорированию шаблона. Использование как это:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

Но обратите внимание, что вы не можете просто вызвать эту toStringфункцию в этой ветке if. поскольку компилятор проверит правильность в обеих ветвях, это может привести к сбою в случаях, когда функция не существует. Один из способов - использовать SFINAE еще раз (enable_if также можно получить из boost):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

Получайте удовольствие, используя его. Преимущество этого в том, что он также работает для перегруженных функций-членов, а также для константных функций-членов (помните, std::string(T::*)() constчто в качестве типа указателя на функцию-член используйте!).

Йоханнес Шауб - Литб
источник
7
Мне нравится, как type_checkиспользуется, чтобы убедиться, что подписи точно совпадают. Есть ли способ сделать так, чтобы он соответствовал любому методу, который мог бы быть вызван так, как Signмог бы быть вызван метод с сигнатурой ? (Например, если Sign= std::string(T::*)(), разрешите std::string T::toString(int default = 42, ...)совпадение.)
j_random_hacker
5
Я просто выяснил кое-что об этом, что не было для меня сразу очевидным, так что в случае, если это поможет другим: chk не является и не должно быть определено! Оператор sizeof определяет размер вывода chk без необходимости вызова chk.
SCFrench
3
@ deek0146: Да, Tне должен быть примитивным типом, поскольку объявление указателя на метод T не относится к SFINAE и приведет к ошибке для любого класса, не относящегося к T. ИМО самое простое решение - объединить его с is_classпроверкой из увеличение.
Ян Худек
2
Как я могу заставить это работать, если моя toStringфункция является шаблонной?
Фрэнк
4
Это (или что-то подобное) в Boost?
Дан Ниссенбаум,
89

С ++ 20 - requires выражения

С C ++ 20 приходят концепции и различные инструменты, такие как requiresвыражения, которые являются встроенным способом проверки существования функции. С их помощью вы можете переписать свою optionalToStringфункцию следующим образом:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pre-C ++ 20 - Набор инструментов для обнаружения

N4502 предлагает инструментарий обнаружения для включения в стандартную библиотеку C ++ 17, которая в итоге вошла в основы библиотеки TS v2. Скорее всего, он никогда не попадет в стандарт, потому что с тех пор он был включен в requiresвыражения, но он все же решает проблему несколько элегантным образом. Инструментарий представляет некоторые метафункции, в том числе, std::is_detectedкоторые можно легко использовать для написания метафункций обнаружения типов или функций поверх них. Вот как вы можете использовать это:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

Обратите внимание, что приведенный выше пример не проверен. Набор инструментов для обнаружения еще не доступен в стандартных библиотеках, но в предложении содержится полная реализация, которую вы можете легко скопировать, если вам это действительно нужно. Это хорошо работает с функцией C ++ 17 if constexpr:

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return "toString not defined";
}

C ++ 14 - Boost.Hana

Boost.Hana очевидно основывается на этом конкретном примере и предоставляет решение для C ++ 14 в своей документации, поэтому я собираюсь процитировать его непосредственно:

[...] Hana предоставляет is_validфункцию, которую можно комбинировать с общими лямбдами C ++ 14, чтобы получить намного более чистую реализацию того же самого:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Это оставляет нам функциональный объект, has_toStringкоторый возвращает, является ли данное выражение допустимым для аргумента, который мы передаем ему. Результат возвращается как IntegralConstant, поэтому constexpr-ness здесь не проблема, так как результат функции в любом случае представляется как тип. Теперь, в дополнение к тому, чтобы быть менее многословным (это один вкладыш!), Цель намного яснее. Другими преимуществами является тот факт, что они has_toStringмогут быть переданы в алгоритмы более высокого порядка, а также могут быть определены в области действия функции, поэтому нет необходимости загрязнять область пространства имен деталями реализации.

Boost.TTI

Еще один идиоматический инструментарий для выполнения такой проверки - хотя и менее элегантный - это Boost.TTI , представленный в Boost 1.54.0. Для вашего примера вам придется использовать макрос BOOST_TTI_HAS_MEMBER_FUNCTION. Вот как вы можете использовать это:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

Затем вы можете использовать bool для создания проверки SFINAE.

объяснение

Макрос BOOST_TTI_HAS_MEMBER_FUNCTIONгенерирует метафункцию, has_member_function_toStringкоторая принимает проверенный тип в качестве первого параметра шаблона. Второй параметр шаблона соответствует типу возвращаемого значения функции-члена, а следующие параметры соответствуют типам параметров функции. Член valueсодержит, trueесли класс Tимеет функцию-членstd::string toString() .

В качестве альтернативы has_member_function_toStringможет принимать указатель на функцию-член в качестве параметра шаблона. Следовательно, его можно заменить has_member_function_toString<T, std::string>::valueна has_member_function_toString<std::string T::* ()>::value.

Morwenn
источник
1
более сжатый, чем 03
ZFY
@ZFY Я думаю, что Boost.TTI тоже работает с C ++ 03, но это наименее элегантное решение из всех.
Морвенн
Действительно ли решение C ++ 20 действительно? Я хотел бы - но это отклонено g ++ и msvc - только принятый clang.
Бернд Бауманнс
в cppreference вы можете прочитать: Если выражение require содержит недопустимые типы или выражения в своих требованиях, и оно не появляется в объявлении шаблонной сущности, то программа некорректна.
Бернд Бауманнс
@BerndBaumanns Действительно? Я заставил его работать со стволом GCC: godbolt.org/z/CBwZdE Может быть, вы правы, я только проверил, что это работает, но не проверял, было ли это законно в соответствии со стандартной формулировкой.
Morwenn
56

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

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

Простое решение для C ++ 11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Обновление, 3 года спустя: (и это не проверено). Чтобы проверить на существование, я думаю, что это будет работать:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}
Аарон МакДейд
источник
4
Это просто и элегантно, но, строго говоря, не отвечает на вопрос OP: вы не позволяете вызывающей стороне проверять существование функции, вы всегда предоставляете ее. Но все равно приятно.
Adrian W
@AdrianW, хорошая мысль. Я обновил свой ответ. Я не проверял это хотя
Аарон МакДейд
В случае, если это поможет кому-то еще, я не смог бы сделать эту работу без template<typename>вариадической перегрузки: это не рассматривалось для разрешения.
Лаборатория Cobotica
Опять же, это неверный C ++ 11.
Питер
29

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

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;
}
Конрад Рудольф
источник
5
Вы должны предпочесть enum для признаков вместо статических констант: «Членами статической константы являются lvalues, что заставляет компилятор создавать и выделять определение для статического члена. В результате вычисления больше не ограничиваются чистым» временем компиляции "эффект"
Озгюр
5
«Значения перечисления не являются lvalues ​​(то есть они не имеют адреса). Так что, когда вы передаете их« по ссылке », статическая память не используется. Это почти точно так же, как если бы вы передавали вычисленное значение как литерал Эти соображения побуждают нас использовать значения перечисления "Шаблоны C ++: Полное руководство
Özgür
22
Comptrol: нет, цитируемый отрывок здесь не применяется, так как статические константы целочисленного типа являются особым случаем! Они ведут себя в точности как перечисление здесь и являются предпочтительным способом. Старый взлом enum был необходим только для компиляторов, которые не следовали стандарту C ++.
Конрад Рудольф
3
@ Роджер Пэйт: Не совсем. «Используется в программе» здесь, по-видимому, является синонимом слова «ссылка». Преобладающее прочтение этого отрывка, которое реализовано всеми современными компиляторами C ++, заключается в том, что вы можете принимать значение статической константы без необходимости ее объявлять (предыдущее предложение гласит: «… член может появляться в виде интегральных константных выражений» ...»). Вам нужно только определить его, если вы берете его адрес (явно через &T::xили неявно, связывая его со ссылкой).
Конрад Рудольф
25

Ну, на этот вопрос уже есть длинный список ответов, но я хотел бы подчеркнуть комментарий Morwenn: есть предложение для C ++ 17, которое делает его действительно намного проще. Смотрите N4502 , но в качестве отдельного примера рассмотрите следующее.

Эта часть является постоянной частью, поместите ее в заголовок.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

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

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

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

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

По сравнению с другими реализациями, описанными выше, эта довольно проста: достаточно сокращенного набора инструментов ( void_tи detect), нет необходимости в волосатых макросах. Кроме того, сообщалось (см. N4502 ), что он заметно более эффективен (время компиляции и потребление памяти компилятором), чем предыдущие подходы.

Вот живой пример . Он отлично работает с Clang, но, к сожалению, версии GCC до 5.1 следовали другой интерпретации стандарта C ++ 11, которая void_tне работала должным образом. Yakk уже предоставил обходной путь: используйте следующее определение 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
аким
источник
Можно ли расширить его для обнаружения функций, не являющихся членами?
plasmacel
Да, конечно. Посмотрите внимательно на примеры: вы в основном предоставляете выражение и проверяете, действительно ли оно. Ничто не требует, чтобы это выражение было только для вызова функции-члена.
аким
N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) - это путь в будущее ... Я искал аккуратный способ обнаружения вещей по типам, а N4502 - это способ идти.
Тлонюк
11

Это решение C ++ 11 для общей проблемы, если «Если бы я сделал X, он бы скомпилировал?»

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

has_to_stringТакая черта has_to_string<T>::valueесть, trueесли и только если Tесть метод, .toStringкоторый может быть вызван с 0 аргументами в этом контексте.

Далее я бы использовал диспетчеризацию тегов:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

который имеет тенденцию быть более понятным, чем сложные выражения SFINAE.

Вы можете написать эти черты с помощью макроса, если вы обнаружите, что делаете это много, но они относительно просты (несколько строк каждая), поэтому, может быть, это того не стоит:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

вышеизложенное создает макрос MAKE_CODE_TRAIT. Вы передаете ему имя желаемой черты и некоторый код, который может проверить тип T. Таким образом:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

создает вышеупомянутый класс черт.

Кроме того, вышеупомянутая методика является частью того, что MS называет «выражением SFINAE», и их компилятор 2013 года терпит неудачу довольно сильно.

Обратите внимание, что в C ++ 1y возможен следующий синтаксис:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

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

Якк - Адам Невраумонт
источник
Это обрабатывает частные случаи?
tower120
@ tower120 Мне бы пришлось поэкспериментировать: как шаблоны взаимодействуют с private / public / protected, мне немного неясно. Однако не имеет значения, куда вы ссылаетесь has_to_string.
Якк - Адам Невраумонт
но вы знаете, если взглянуть с другой стороны ... Мы можем связаться с защищенными членами из класса Derived. Может быть, если поместить все эти вещи в класс INSIDE и конвертировать из структур в функции constexpr ...
tower120
Вот, посмотрите на этот coliru.stacked-crooked.com/a/ee94d16e7c07e093 Я просто не могу сделать это constexpr
tower120
@ tower120 C ++ 1y заставляет это работать: coliru.stacked-crooked.com/a/d8cdfff24a171394
Якк - Адам Невраумонт
10

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

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

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

Проверьте функцию члена void x():

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

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

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

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

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Проверить членство в профсоюзе x:

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

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

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Проверьте любую функцию-член xнезависимо от подписи:

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

ИЛИ

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

Детали и ядро:

/*
    - Multiple inheritance forces ambiguity of member names.
    - SFINAE is used to make aliases to member names.
    - Expression SFINAE is used in just one generic has_member that can accept
      any alias we pass it.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

template<typename A, typename = void>
struct got_type : std::false_type {};

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(0)) == 1)
        , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
    );

    static bool const value = sizeof(f<Alias>(0)) == 2;
};

Макросы (Эль Диабло!):

CREATE_MEMBER_CHECK:

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

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_SIG_CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

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

CREATE_MEMBER_UNION_CHECK:

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

CREATE_MEMBER_ENUM_CHECK:

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

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::value    \
    ;                                           \
}

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)
Бретт Россиер
источник
1
Есть ли у вас какие-либо идеи, почему, если мы перейдем sig_check<func_sig, &T::func_name>к бесплатной проверке функций: sig_check<func_sig, &func_name>она не будет собрана с «необъявленным идентификатором» с упоминанием имени функции, которую мы хотим проверить? потому что я ожидал бы, что SFINAE сделает это НЕ ошибкой, он делает это только для членов, почему не для свободных функций?
v.oddou
Я предполагаю, что это как-то связано с тем, что свободная функция не является классом или структурой. Этот метод определения присутствия члена в действительности основан на механизме множественного наследования в C ++, что вызывает неоднозначность между классом-заглушкой, который существует только с целью размещения члена, который вы проверяете, по сравнению с классом, который вы фактически проверяете для члена в. Это интересный вопрос, хотя, не думал об этом. Вы можете проверить другие методы проверки членов C ++ 11/14, я видел некоторые умные вещи в новом стандарте.
Бретт Россиер,
Спасибо за ваш ответ, я думаю, что мне, возможно, придется более подробно проверить предоставленную вами информацию о наследовании, потому что до сих пор я не видел какой-либо корреляции между простым положением на SFINAE для создания выражения, которое было бы неверным при выражении доступа к член в параметре типа шаблона и множественное наследование. Но я полностью верю, что в C ++ даже отдаленные понятия могут кровоточить друг на друге. Теперь для бесплатных функций этот вопрос интересен: stackoverflow.com/questions/26744589 TC-ответ, кажется, использует хитрость объявления фиктивной
фигуры,
8

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

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

Вот несколько примеров из этого решения:

Example1:

Мы проверяем участника со следующей подписью: T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

Обратите внимание, что он даже проверяет константность метода, а также работает с примитивными типами. (Я имею в видуhas_const_begin<int>::value false и не вызывает ошибку во время компиляции.)

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

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

kispaljr
источник
7

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

Вот альтернатива решению Никола Бонелли, которое не опирается на нестандартный typeofоператор.

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

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


обновление - 7 ноября 2008 г .:

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

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

Пункт 3

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

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

Поведение GCC и Digital Mars выглядит корректно - в обоих случаях toString()функция, не являющаяся членом, связана с вызовом.

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


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}
Майкл Берр
источник
1
Нет, он не соответствует стандартам, хотя я думаю, что он будет работать в GCC, если вы включите опцию -fpermissive.
Леон Тиммерманс
Я знаю, что комментарии не дают много места, но не могли бы вы указать информацию о том, почему они не соответствуют стандартам? (Я не спорю - мне любопытно)
Майкл Берр
Майк Б: стандарт говорит в 3.10 стр. 15: «Если программа пытается получить доступ к сохраненному значению объекта через lvalue другого, чем один из следующих типов, поведение не определено», и этот список действительно не включает случай, который вы делать.
Йоханнес Шауб -
4
Я не уверен, почему он не добавляет еще один мой комментарий: ваш вызов toString не квалифицирован. поэтому он всегда будет вызывать функцию free, а не функцию base, поскольку базовый класс зависит от параметра типа шаблона.
Йоханнес Шауб -
@litb: Спасибо за указатели. Я не думаю, что 3.10 применяется здесь. Вызов toString () внутри doToString () не является «доступом к сохраненному значению объекта через lvalue». Но ваш второй комментарий правильный. Я обновлю ответ.
Майкл Берр
6

Стандартное решение C ++, представленное здесь litb, не будет работать должным образом, если метод будет определен в базовом классе.

Для решения, которое обрабатывает эту ситуацию, обратитесь к:

На русском языке: http://www.rsdn.ru/forum/message/2759773.1.aspx

Перевод на английский Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

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

В Visual Studio я заметил, что при работе с методом, не имеющим аргументов, необходимо добавить дополнительную пару избыточных () вокруг аргументов, чтобы вывести () в выражении sizeof.


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

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

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

шишка
источник
6

Пример использования 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");
Пол Белангер
источник
5

Я изменил решение, представленное в https://stackoverflow.com/a/264088/2712152 чтобы сделать его более общим. Кроме того, поскольку он не использует какие-либо новые функции C ++ 11, мы можем использовать его со старыми компиляторами и также должны работать с msvc. Но компиляторы должны позволить C99 использовать это, так как он использует переменные макросы.

Следующий макрос может быть использован для проверки, имеет ли определенный класс конкретный typedef или нет.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

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

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Мы можем использовать вышеупомянутые 2 макроса, чтобы выполнить проверки для has_typedef и has_mem_func как:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}
Shubham
источник
Вы можете улучшить это, чтобы поддерживать функции-члены с аргументами шаблона. Измените шаблон <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" ); };
ACyclic
4

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

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

Александр С.
источник
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

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

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

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

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

Итак, я придумал версию, которая использует только sizeof ():

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

Демонстрационная версия (с расширенной проверкой типов возвращаемых данных и обходным путем vc ++ 2010): http://cpp.sh/5b2vs

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

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

user3296587
источник
Нет, но я объявляю это сам, и он не использует rvalue (посмотрите на верхнюю часть моего кода). Или вы можете просто убедить себя и попробовать живую демонстрацию в режиме c ++ 98. PS: static_assert тоже не c ++ 98, но есть обходные пути (живая демонстрация)
user3296587
d'ах! пропустил это. :-)
Ян Ни-Льюис
Ваши статические утверждения не работают. Вам нужно использовать размер массива -1 вместо 0 (попробуйте поставить static_assert(false);). Я использовал это в связи с CRTP, где я хочу определить, имеет ли производный класс определенную функцию - что, оказывается, не работает, но ваши утверждения всегда проходили. Я потерял немного волос к этому.
свинья
Я предполагаю, что вы используете g ++. Обратите внимание, что gcc / g ++ имеет расширение, которое допускает массив нулевого размера ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587
Не могли бы вы переписать это, чтобы не перегружать оператор? например, выбрать другого оператора? Кроме того, избегайте загрязнения пространства имен чем-либо, кроме has_awesome_member?
einpoklum
1

Вот моя версия, которая обрабатывает все возможные перегрузки функций-членов с произвольной арностью, включая функции-члены шаблона, возможно, с аргументами по умолчанию. Он различает 3 взаимоисключающих сценария при вызове функции-члена некоторого типа класса с заданными типами аргументов: (1) допустимый или (2) неоднозначный или (3) нежизнеспособный. Пример использования:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

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

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

Вот код, написанный на c ++ 11, однако вы можете легко перенести его (с небольшими изменениями) на не-c ++ 11, который имеет расширения typeof (например, gcc). Вы можете заменить макрос HAS_MEM своим собственным.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif

Hui
источник
1

Вы можете пропустить все метапрограммирования в C ++ 14, а просто пишу это , используя fit::conditionalиз Fit библиотеки:

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

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

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

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

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);
Пол Фульц II
источник
1
Насколько легко это написать, чтобы не зависеть от fitкакой-либо другой библиотеки, кроме стандартной?
einpoklum
1

С C ++ 20 вы можете написать следующее:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}
Бернд Бауманнс
источник
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
}
tereshkd
источник
0

У меня была аналогичная проблема:

Шаблонный класс, который может быть получен из нескольких базовых классов, некоторые из которых имеют определенный член, а другие - нет.

Я решил это аналогично ответу «typeof» (Никола Бонелли), но с помощью decltype, чтобы он правильно компилировался и работал на MSVS:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}
Игаль Эйлам
источник
0

Еще один способ сделать это в C ++ 17 (вдохновленный boost: hana).

Напишите это один раз и используйте много раз. Это не требует has_something<T>типа черты классов.

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

пример

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}
Дмитрий Овдиенко
источник
-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}
Абхишек
источник
6
«Нам не нужны описания ответов» ... пожалуйста, добавьте информативное описание к вашему ответу, чтобы улучшить его. Спасибо.
YesThatIsMyName