Запропоновано перевірити наявність функції члена класу?


498

Чи можна написати шаблон, який змінює поведінку залежно від того, чи визначена певна функція члена в класі?

Ось простий приклад того, що я хотів би написати:

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 - ні , вам скажуть, щоString не визначено.
Аліса Перселл

Можливий дублікат Як перевірити, чи існує ім’я члена (змінна чи функція) у класі, із зазначенням типу або без нього? , оскільки вона охоплює більш широку проблему з 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
Хоча я використав наступні для "один" та "два": typedef char Small; class Big {char dummy [2];}, щоб уникнути неоднозначності щодо змінного розміру залежної від платформи.
user23167

6
Я сумніваюся, що існує на землі платформа з розміром (char) == sizeof (long)
Нікола Бонеллі

17
Я не зовсім впевнений, але не думаю, що це портативно. typeof - це розширення GCC, це не працюватиме для інших компіляторів.
Леон Тіммерманс

56
typeof не потрібен - також працює char [sizeof (& C :: helloworld)]. А щоб уникнути sizeof (long) == sizeof (char), використовуйте структуру {char [2]} ;. Він повинен мати розмір> = 2
MSalters

57
Тривіально, але мені знадобилося певний час, щоб зрозуміти: замінити typeofна decltypeвикористання C ++ 0x , наприклад, через -std = c ++ 0x.
грудня 11

264

Це питання давнє, але з 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його можна використовувати.


4
Перевага decltypeнад sizeofтим, що тимчасове не вводиться спеціально розробленими правилами для викликів функцій (тому вам не потрібно мати прав доступу до деструктора типу повернення і не спричиняти неявну інстанцію, якщо тип повернення є екземпляр шаблону класу).
Йоханнес Шауб - ліб

5
Microsoft ще не впровадила Expression SFINAE у своєму компіляторі C ++. Просто думаю, що я можу допомогти заощадити деяким людям час, тому що мене розгубило, чому це не працює для мене. Хоча приємне рішення, не можу дочекатися його використання у Visual Studio!
Джонатан

3
Ваш перший приклад посилання порушено
NathanOliver

1
Слід сказати, що static_assert(has_stream<X, char>() == true, "fail X");буде компілюватись, а не стверджувати, тому що char перетворюється на int, тож якщо така поведінка не потрібна, і хочеться, щоб всі типи аргументів збігалися, я не знаю, як цього можна досягти?
Габріель

4
Якщо ви настільки спантеличені, як і я, на два аргументи дельтипування: 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функцію в цій гілці. оскільки компілятор перевірить достовірність в обох гілках, це не вдасться у випадках, коли функція не існує. Один із способів - використовувати 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.";
}

Весело використовуючи його. Його перевага полягає в тому, що він також працює для перевантажених функцій членів, а також для функцій-членів const (пам’ятайте, використовуйте 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не повинен бути примітивним типом, тому що декларація вказівника на метод Т не підпадає під дію SFINAE і вимикає помилки для будь-якого некласового T. IMO найпростіше рішення - поєднувати з is_classперевіркою від прискорення.
Ян Худець

2
Як я можу зробити цю роботу, якщо моя toStringфункція є шаблоном?
Френк

4
Це (або щось еквівалентне) в Boost?
Дан Ніссенбаум

89

C ++ 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 у своїй документації, тому я збираюся його цитувати безпосередньо:

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

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

Це залишає нам об'єкт функції, has_toStringякий повертає, чи є даний вираз дійсним для аргументу, який ми йому передаємо. Результат повертається як an IntegralConstant, тому constexpr-ності тут не є проблемою, оскільки результат функції так чи інакше представлений у вигляді типу. Тепер, окрім того, що вона є менш дослівною (це один лайнер!), Намір набагато чіткіший. Іншими перевагами є той факт, що has_toStringможна передати алгоритми вищого порядку, і його також можна визначити в області функцій, тому немає необхідності забруднювати область простору імен деталями реалізації.

Підвищення.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.


1
більш лаконічна, ніж 03
ZFY

@ZFY Я думаю, що Boost.TTI також працює з C ++ 03, але це найменш елегантне рішення партії.
Морвен

Чи дійсно рішення C ++ 20 дійсне? Мені б хотілося, але це відхиляється g ++ та msvc - приймається лише clang.
Бернд Бауманс

на cppreference ви можете прочитати: Якщо вираз-вираз містить недійсні типи або вирази в своїх вимогах, і він не відображається в декларації шаблонного об'єкта, програма непрацює.
Бернд Бауманс

@BerndBaumanns Дійсно? Я змусив його працювати зі стволом GCC: godbolt.org/z/CBwZdE Можливо, ти маєш рацію, я лише перевірив, що він працює, але не перевірив, чи законний він відповідно до стандартної редакції.
Морвенн

56

Хоча це питання два роки, я наважуюся додати свою відповідь. Сподіваємось, це уточнить попереднє, безперечно відмінне, рішення. Я прийняв дуже корисні відповіді Нікола Бонеллі та Йоханнеса Шауба і об'єднав їх у рішення, яке є 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, це рішення приховує всю логіку всередині, і Конрад покладає частину тягаря на користувача. Незважаючи на те, що він короткий і набагато читаєший, рішення Конрада вимагає окремої спеціалізації шаблонів для кожного класу toString. Якщо ви пишете загальну бібліотеку, яка бажає працювати з будь-яким класом там (придумайте щось на зразок підвищення), то вимагати від користувача визначення додаткових спеціалізацій деяких незрозумілих шаблонів може бути неприйнятним. Іноді бажано написати дуже складний код, щоб публічний інтерфейс був максимально простим.
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
Це просто і елегантно, але строго кажучи не відповідає на питання ОП: ви не даєте можливість абоненту перевірити наявність функції, ви завжди надаєте її. Але приємно все одно.
Адріан Ш

@AdrianW, хороший момент. Я оновив свою відповідь. Я його ще не перевіряв
Аарон Макдейд

У випадку, якщо це допомагає комусь іншому, я не зміг би зробити цю роботу без template<typename>різного перевантаження: він не розглядався для вирішення.
Laboratorio 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 для ознак замість статичних констант: "Статичні постійні члени - це значення, що змушує компілятор інстанціювати та виділяти визначення для статичного члена. В результаті обчислення вже не обмежується чистим" часом компіляції. "ефект".
Özgür

5
"Значення перерахунку не є значеннями (тобто не мають адреси). Отже, коли ви передаєте їх" за посиланням ", статична пам'ять не використовується. Це майже точно так, як якщо б ви передали обчислене значення як буквальне Ці міркування мотивують нас використовувати значення перерахування "C ++ Шаблони: Повне керівництво
Özgür

22
Контроль: ні, цитований уривок тут не застосовується, оскільки статичні константи типу цілого типу - особливий випадок! Вони поводяться саме так, як енмум і є кращим способом. Старий перелом enum був необхідний лише для компіляторів, які не відповідають стандарту C ++.
Конрад Рудольф

3
@Roger Pate: Не зовсім. "Використовується в програмі" тут, мабуть, є синонімом "посилання". Читання цього уривка, що переважає, і того, що реалізується всіма сучасними компіляторами C ++, полягає в тому, що ви можете приймати значення статичної константи, не потребуючи її декларувати (попереднє речення говорить так: "... член може з'являтися в цілісних постійних виразах" … ”). Ти тільки повинні визначити, якщо ви берете його адресу (явно через &T::xабо неявно шляхом зв'язування його з еталоном).
Конрад Рудольф


25

Ну, на це питання вже є довгий перелік відповідей, але я хотів би наголосити на коментарі Морвенна: є пропозиція щодо С ++ 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

Чи можна розширити його на виявлення функцій, які не належать до членів?
плазмацел

Так, звичайно. Подивіться уважно на приклади: ви в основному надаєте вираз і перевіряєте, чи він дійсний. Ніщо не вимагає, щоб цей вираз стосувався лише виклику функції члена.
аким

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) - це шлях майбутнього ... Я шукав акуратний спосіб виявлення речей за типами, а N4502 - спосіб йти.
tlonuk

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 ++. Робити це, мабуть, не варто, оскільки користь (від того, що код є вбудованим) не коштує витрат (до того, як ніхто не розуміє, як це працює), але існування цього вище рішення може представляти інтерес.


Чи справляється це з приватними справами?
башта120

@ tower120 Мені доведеться експериментувати: як шаблони взаємодіють із приватним / публічним / захищеним, для мене трохи незрозуміло. Не важливо, куди ви посилаєтеся has_to_string.
Якк - Адам Невраумон

але ви знаєте, якщо подивитися з іншого боку ... Ми можемо охопити захищених членів з класу Derived. Можливо, якщо поставити все це в клас INSIDE та перетворити з конструкцій на функції contexpr ...
tower120

Ось подивіться на цей coliru.stacked-crooked.com/a/ee94d16e7c07e093 Я просто не можу зробити це constexpr
tower120

@ tower120 C ++ 1y змушує його працювати: coliru.stacked-crooked.com/a/d8cdfff24a171394
Yakk - Adam Nevraumont

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

Макроси (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>вона не вдається побудувати за допомогою "незадекларованого ідентифікатора" із зазначенням назви функції, яку ми хочемо перевірити? тому що я б очікував, що SFINAE зробить це НЕ помилкою, він робить саме це для членів, а чому б не для безкоштовних функцій?
v.oddou

Я припускаю, що це має щось спільне з тим, що вільна функція - це не клас чи структура. Ця методика виведення присутності члена дійсно орієнтована на механізм множинного успадкування в C ++, примушуючи неоднозначність між класом stub, який існує лише з метою розміщення члена, який ви перевіряєте, проти класу, який ви фактично перевіряєте на члена дюйм. Це цікаве запитання, але не замислювався над цим. Ви можете ознайомитись з іншими C ++ 11/14 методами перевірки, я помітив деякі розумні речі в новому стандарті.
Бретт Россьє

Дякую за вашу відповідь, я думаю, що мені, можливо, доведеться більш глибоко перевірити інформацію, яку ви даєте про спадщину, тому що до цього часу я не бачив ніякої кореляції між простою покладанням на SFINAE, щоб зробити вираз, який би не був правильним, висловлюючи доступ до член у параметрі типу шаблону та множинному успадкуванні. Але я повністю вірю, що в C ++ навіть віддалені концепції можуть кровоточити одна в одну. Тепер для вільних функцій це питання цікаве: stackoverflow.com/questions/26744589 TC відповідь, здається, використовує трюк оголошення манекена, щоб уникнути "незадекларованого ідентифікатора"
v.oddou

8

Відповідь на це я написав в іншій темі, яка (на відміну від рішень вище) також перевіряє успадковані функції членів:

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 не повинен бути конструктованим за замовчуванням або задовольняти будь-яку спеціальну концепцію. Методика працює і з учасниками шаблону.

Я з нетерпінням чекаю думок з цього приводу.


7

Тепер це було приємно загадка - чудове запитання!

Ось альтернатива рішенню Нікола Бонеллі, яке не покладається на нестандартного typeofоператора.

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

Більш довгий блок коментарів містить деталі про те, як він працює (або повинен працювати). Як мовиться, я не впевнений, яка поведінка відповідає стандартам - я б вітаю коментар до цього.


оновлення - 7 листопада 2008 року:

Схоже, що цей код синтаксично правильний, поведінка, яку демонструють MSVC та Comeau C ++, не відповідає стандарту (дякую Леону Тіммермансу та ламб, що вказав на мене в правильному напрямку). Стандарт 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, якщо увімкнути опцію -fmismissive.
Леон Тіммерманс

Я знаю, що коментарі не дають багато місця, але ви могли б вказати на інформацію, чому це не відповідає стандартам? (Я не сперечаюся - мені цікаво)
Майкл Берр

Майк B: стандарт говорить у 3.10 p15: "Якщо програма намагається отримати доступ до збереженого значення об'єкта через значення, яке не є одним із наведених нижче типів, поведінка не визначена", і цей список дійсно не включає випадок, коли ви робити.
Йоханнес Шауб - ліб

4
Я не впевнений, чому він не додає ще одного коментаря до мене: ваш дзвінок наString є некваліфікованим. тому він завжди буде називати вільну функцію і ніколи не функцію в базі, оскільки базовий клас залежить від параметра типу шаблону.
Йоханнес Шауб - ліб

@litb: Дякую за покажчики. Я не думаю, що тут застосовується 3.10. Виклик toString () всередині doToString () - це не "доступ до збереженого значення об'єкта через значення". Але ваш 2-й коментар правильний. Я оновлю відповідь.
Майкл Берр

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 ). Разом із типовим підходом до Nicola я міг створити перевірку GCC та MSVC, як шукала ОП.

Оновлення: Джерело можна знайти тут


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 використовувати це, оскільки він використовує різноманітні макроси.

Наступний макрос можна використовувати для перевірки того, чи має певний клас певний типdede чи ні.

/** 
 * @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;
}

Ви можете покращити це для підтримки функцій членів за допомогою аргументів шаблону. Змініть шаблон <typename T> на шаблон <typename T, typename ... Args>, тоді ви можете використовувати "Args ..." у вашому макрос еліпсісі для створення контрольної структури з різноманітними аргументами шаблону. напр. Виявити метод "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;
};

Ви повинні переконатися, що Т - клас. Здається, що неоднозначність у пошуку 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


Чи є можливість вкласти 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 { };

Збій, якщо toStringперевантажений, як &U::toStringі неоднозначно.
Якк - Адам Невраумон

@Yakk Я думаю, що актор може вирішити цю проблему.
користувач1095108

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

Ніякого джерела, як я сам це придумав.

Під час запуску демонстрації в реальному часі на компіляторі g ++, зауважте, що розміри масивів 0 дозволені, це означає, що використовуваний static_assert не спровокує помилку компілятора, навіть коли він не працює.
Поширена обробка - замінити "typedef" у макросі на "extern".


Ні, але я декларую це сам, і він не використовує rvalue (дивіться вгорі мого коду). Або ви можете просто переконати себе та спробувати демо-версію в режимі c ++ 98. PS: static_assert теж не c ++ 98, але є робочі обходи (live demo)
користувач3296587

d'oh! пропустив це. :-)
Ян Ні-Льюїс

Ваші статичні твердження не працюють. Потрібно використовувати масив розміром -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, однак, ви можете легко перенести його (з незначними налаштуваннями) до non-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


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()
);

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
}

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.