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

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

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

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

Конечно, само собой разумеется, что ответ (ы) шаблона ниже работает только с информацией времени компиляции, то есть T должен иметь toString. Если вы передадите подкласс T, который делает определяет toString, но T делает нет, вам сообщат, что toString не определено.

Alice Purcell 02.09.2010 13:50

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

iammilind 18.03.2016 12:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
534
2
172 251
31
Перейти к ответу Данный вопрос помечен как решенный

Ответы 31

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

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

вы должны предпочесть enum для признаков вместо статических констант: «Статические члены-константы - это lvalue, что заставляет компилятор создавать экземпляр и выделять определение для статического члена. В результате вычисления больше не ограничиваются чистым» временем компиляции " эффект."

Özgür 20.03.2009 12:28

«Значения перечисления не являются lvalue (то есть у них нет адреса). Таким образом, когда вы передаете их« по ссылке », статическая память не используется. Это почти так же, как если бы вы передали вычисленное значение как буквальное . Эти соображения побуждают нас использовать значения перечисления "C++ Templates: The Complete Guide

Özgür 20.03.2009 12:29

Comptrol: нет, процитированный отрывок здесь не применяется, поскольку статические константы целочисленного типа - это особый случай! Здесь они ведут себя как точно как перечисление и являются предпочтительным способом. Старый хакер enum был необходим только для компиляторов, которые не следовали стандарту C++.

Konrad Rudolph 23.03.2009 12:07

@Konrad: К сожалению, это не так. «Этот член все равно должен быть определен в области пространства имен, если он используется в программе ..» [C++ 03 9.4.2 / 4]

Roger Pate 10.05.2010 06:03

@ Роджер Пейт: Не совсем. «Используется в программе» здесь, очевидно, является синонимом «упоминается». Преобладающее прочтение этого отрывка и тот, который реализован всеми современными компиляторами C++, заключается в том, что вы можете взять ценить статической константы без необходимости ее объявлять (в предыдущем предложении говорится следующее: «… член может появляться в выражениях интегральных констант. … »). Вам Только необходимо определить его, если вы берете его адрес (явно через &T::x или неявно, привязав его к ссылке).

Konrad Rudolph 10.05.2010 14:44

@Roger Pate: Кроме того, это использование (явно без определение) является обычной практикой в ​​известных библиотеках C++, например. в нескольких библиотеках Boost (Boost.MPL, чтобы назвать только одну).

Konrad Rudolph 10.05.2010 14:47

«использованный» относится к определению «использованный» в одном правиле определения. Например, выражение 1 ? a : aиспользует a.

Johannes Schaub - litb 28.05.2010 02:07

@Johannes Schaub - litb: Интересно - значит, это означает, что Boost, строго говоря, не соответствует стандартам? Будет ли предложенная резолюция частью следующего стандарта? Под пояснением «обзор» говорится, что рабочая группа достигла неформального консенсуса.

Konrad Rudolph 28.05.2010 15:16

@Konrad boost, похоже, дает нестандартные определения. Быстрый тест показывает, что &boost::mpl::int_<0>::value компилируется нормально.

Johannes Schaub - litb 28.05.2010 15:24

@Johannes Schaub - litb: В общем, нет. int_::value кажется исключением, если он компилируется. Остальные однозначно не давать определений. Некоторые библиотеки, кажется, используют для принятия решения флаг времени компиляции BOOST_NO_INCLASS_MEMBER_INITIALIZATION.

Konrad Rudolph 28.05.2010 18:16

@Konrad, инициализация внутри класса не исключает внеклассного определения. Таким образом, этот макрос, кажется, просто говорит, что он не будет или будет инициализироваться в классе, но, похоже, он ничего не говорит о предоставляемом внеклассном определении. Ссылка указывает на тест, вероятно, они уже знали, что не имеют доступа к свойствам объекта констант, и поэтому не предоставили определения. Но я, конечно, не скажу, что все ускорение соответствует, наверняка где-то спрятаны несоответствующие биты ^^

Johannes Schaub - litb 28.05.2010 18:46

@Johannes Schaub - litb: «инициализация внутри класса не исключает внеклассного определения» Я знаю, но макрос использовался именно в контексте определений вне класса (с внутренней инициализацией) в каком-то файле. Название плохо отражает это, конечно.

Konrad Rudolph 28.05.2010 19:06
Как вы это используете? Как можно заставить условную компиляцию проверять значение логического has_tostring?
bobobobo 09.12.2012 16:50
Вот пример того, как использовать type traits для условной компиляции в C++ 11.
bobobobo 09.12.2012 17:04
Ответ принят как подходящий

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

Ах, ты меня опередил! Это очень хороший трюк (подтверждено, что работа с GCC 4.1 на Mac OSX 10.5)

user23167 03.11.2008 00:28

Хотя я использовал для «один» и «два» следующее: typedef char Small; class Big {char dummy [2];}, чтобы гарантировать отсутствие двусмысленности в отношении размера переменной, зависящей от платформы.

user23167 03.11.2008 00:40

Я сомневаюсь, что на земле существует платформа с sizeof (char) == sizeof (long)

Nicola Bonelli 03.11.2008 00:46

Что ж, действительно, но строго говоря, это не гарантируется, если вы действительно соблюдаете стандарты. (P.S. Я видел платформы, на которых это было так, но компиляторы все равно подавились бы этим кодом;))

user23167 03.11.2008 00:47

Я не совсем уверен, но не думаю, что это портативно. typeof - это расширение GCC, это не будет работать с другими компиляторами.

Leon Timmermans 03.11.2008 00:52

Если вас действительно беспокоит тот размер, который может совпадать, использование boost :: is_same может быть более надежным.

Leon Timmermans 03.11.2008 00:55

Леон, как ты думаешь, можно ли использовать typeof () другим методом? Я думал о tr1 :: results_of ...

Nicola Bonelli 03.11.2008 13:23

typeof не нужен - char [sizeof (& C :: helloworld)] тоже работает. А чтобы избежать sizeof (long) == sizeof (char), используйте struct {char [2]} ;. Он должен иметь размер> = 2.

MSalters 02.02.2009 12:03

Я немного переработал это решение, чтобы оно не требовало typeof. Посмотрите мой ответ внизу этой страницы. На самом деле я объединил ответы Никола и Йоханнеса и попытался дать полезные имена всем конструкциям. Надеюсь, это поможет.

FireAphis 02.09.2010 18:49

Тривиально, но мне потребовалось время, чтобы понять: заменить typeof на decltype при использовании C++ 0x, например, через -std = C++ 0x.

hrr 10.06.2011 20:46

@hrr Спасибо, сэр, это исправила мою ошибку компилятора под Linux для C++ 0x.

Boris Rusev 12.07.2012 02:30

decltype заменяет typeof в C++ 0x. Однако решение не компилируется с decltype (decltype не может разрешить адрес перегруженной функции).

Michael 02.05.2013 15:43

Я не уверен, почему ваше решение имеет два параметра шаблона (T в классе и C в методе).

CashCow 29.12.2014 11:43

@CashCow "Дополнительный" typename C в методах test требуется из-за того, как работает SFINAE. Если бы вы использовали T, компилятор вообще не смог бы создать экземпляр шаблона, и код не скомпилировался бы (потому что не существует действующей версии has_helloworld<Generic>).

Max Truxa 15.05.2015 19:43

@Michael Это должно сработать, если вы используете decltype(C::helloworld)* вместо decltype(&C::helloworld).

Max Truxa 15.05.2015 19:49

Как проверить наличие helloworld, если это шаблон функции? typeof (& C :: helloworld) в этом случае завершится ошибкой

0x2207 20.08.2015 22:43

Это также не сработает, если helloworld перегружен.

0x2207 20.08.2015 22:51

Если кто-то запутался насчет ..., посмотрите stackoverflow.com/questions/39664453/…

陳 力 20.05.2018 10:27

@NicolaBonelli, ошибаюсь. В некоторых системах Cray есть процессоры, которые используют 64 регистра по 64 бита каждый и не предлагают байты. Целочисленные типы их компиляторов C / C++ 64-битные. Эти машины очень быстро вычисляют матрицы и тому подобное, но медленно компилируют код (который требует байтов и, следовательно, большого количества сдвигов ... в конце концов, все же намного быстрее, чем ваш маленький x86-совместимый процессор ... :-))

Alexis Wilke 22.04.2019 01:59

Я поставил decltype вместо typeof, и пример работает. Но если я попытаюсь определить начало с vector <int>, это не сработает. (конечно, я написал & C :: begin, а также проверил предложение Макса Truxa с decltype (C :: begin) *, но никак. Что можно сделать в этом случае?

crillion 14.07.2020 11:49

@crillion У меня только что была эта проблема, и кто-то написал здесь ответ: stackoverflow.com/a/63818399/4602726

TommyD 09.09.2020 22:41

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

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

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

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


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

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

14.6.2 Dependent names [temp.dep]

Paragraph 3

In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

Итак, похоже, что когда 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;
}

Нет, это не соответствует стандартам, хотя я думаю, что он будет работать в GCC, если вы включите параметр -fpermissive.

Leon Timmermans 05.11.2008 01:50

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

Michael Burr 05.11.2008 08:30

Майк Б: стандарт говорит в 3.10 p15: «Если программа пытается получить доступ к сохраненному значению объекта через lvalue, отличное от одного из следующих типов, поведение не определено», и этот список действительно не включает случай, который вы делать.

Johannes Schaub - litb 07.11.2008 11:17

Я не уверен, почему он не добавляет еще один мой комментарий: ваш вызов toString неквалифицирован. поэтому он всегда будет вызывать бесплатную функцию, а не функцию в базе, поскольку базовый класс зависит от параметра типа шаблона.

Johannes Schaub - litb 07.11.2008 11:27

@litb: Спасибо за указатели. Я не думаю, что здесь применяется 3.10. Вызов toString () внутри doToString () не означает «доступ к сохраненному значению объекта через lvalue». Но ваш 2-й комментарий правильный. Я обновлю ответ.

Michael Burr 07.11.2008 21:49

Вы не знаете, является ли doToString виртуальным в базовом классе. Если это так, то вы получаете доступ к v-таблице объекта.

Johannes Schaub - litb 09.11.2008 01:28

подождите, у меня есть явная цитата из стандарта об этом: 9.3.1 / 1: «Если нестатическая функция-член класса X вызывается для объекта, который не относится к типу X или к типу, производному от X, поведение не определено ". Это просто удача, кто-то процитировал это и сказал мне, откуда это у него :)

Johannes Schaub - litb 15.11.2008 21:18

C++ позволяет использовать для этого СФИНАЕ (обратите внимание, что с функциями 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 также можно получить из ускорения):

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

какой chk используется в макросе?

lurscher 26.10.2010 18:14

@lurscher - это перегруженная функция, где первая перегрузка возвращает ссылку на массив char размером 1, а другая - размером 2. Используя SFINAE, один замаскирован.

Johannes Schaub - litb 26.10.2010 22:52

Мне нравится, как используется type_check, чтобы гарантировать точное совпадение подписей. Есть ли способ сделать так, чтобы он соответствовал любому методу, который можно было бы вызвать, как можно было бы вызвать метод с сигнатурой Sign? (Например, если Sign = std::string(T::*)(), разрешить совпадение std::string T::toString(int default = 42, ...).)

j_random_hacker 17.11.2010 04:37

Я просто выясняю кое-что об этом, что не было очевидным для меня, поэтому на случай, если это поможет другим: chk нет и не нуждается в определении! Оператор sizeof определяет размер вывода chk без необходимости вызывать chk.

SCFrench 23.01.2011 20:24

Мне это нравится, но выдает ошибку, если T - void *, есть ли способ обойти это? (Я думаю, что это могут быть все основные типы, но мне было бы достаточно иметь особый случай для void *)

deek0146 30.06.2011 04:27

@ deek0146: Да, T не должен быть примитивным типом, потому что объявление указателя на метод T не подчиняется SFINAE и приведет к ошибке для любого некласса T. IMO самое простое решение - объединить с is_class чек от буста.

Jan Hudec 22.05.2012 16:04

Как я могу заставить это работать, если мой toString - это шаблонная функция?

Frank 20.08.2012 06:00

@frank вы можете многое проверить о шаблоне функции. Чем ты интересуешься?

Johannes Schaub - litb 20.08.2012 11:11

Это (или что-то подобное) в Boost?

Dan Nissenbaum 29.03.2013 10:24

Это великолепно! Могу ли я узнать, почему тип возвращаемого значения - «да» и «нет», а не «да» и «нет»? Спасибо

Viet 24.05.2013 07:18

@Viet: Потому что вы не можете вернуть массив на C или C++. (Альтернативой использованию ссылки является возврат структуры, содержащей массив, вы видите это в других ответах)

Ben Voigt 09.06.2013 05:32

Я отредактировал пост, чтобы он работал с HAS_MEM_FUNC(operator -,someFuncName). Угадайте, что один крошечный пробел делает имеет значение :)

Paweł Stawarz 27.03.2014 02:55

@ PawełStawarz, на каком компиляторе это не удается? Я думал, что вы получите ->, только если вы сказали funC## >?

Johannes Schaub - litb 27.03.2014 03:00

@ JohannesSchaub-litb Visual Studio 2k8, только что протестировал с HAS_MEM_FUNC(operator -,hasSub)

Paweł Stawarz 27.03.2014 03:02

@ PawełStawarz вау, это странно. А что насчет coliru.stacked-crooked.com/a/d686eb1c15df6740?

Johannes Schaub - litb 27.03.2014 03:03

@ JohannesSchaub-litb, который отлично компилируется.

Paweł Stawarz 27.03.2014 03:04

@ PawełStawarz Я думаю, тогда вы могли столкнуться с ошибкой в ​​2k8. Clang соглашается с GCC, что это плохо сформировано. Я сохраню ответ вместе с вашей правкой, так как теперь он работает еще для одного компилятора =)

Johannes Schaub - litb 27.03.2014 03:05

@ JohannesSchaub-litb, возможно, я обнаружил какую-то ошибку. У 2k8 есть много таких :) Тем не менее - сохранение изменений может только помочь людям, застрявшим на VS2k8. Если хотите - можете отменить изменения. Я просто подумал, что это будет полезно. Боту: с удовольствием :)

Paweł Stawarz 27.03.2014 03:08

По какой-то причине в gcc type_check<Sign, &_1::func > не работает стабильно (принимается только первое совпадение сигнатуры, если есть несколько методов с одинаковой синатурой), но type_check<Sign, &_1::func > работает?

Swift - Friday Pie 27.11.2019 19:16

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

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

По-русски : http://www.rsdn.ru/forum/message/2759773.1.aspx

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

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

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

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

Johannes Schaub - litb 08.07.2009 23:56

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

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

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

Просто интересно, делает ли это что-то, чего не делает приведенное ниже решение Конрада Рудольфа?

Alastair Irvine 12.12.2013 18:52

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

FireAphis 15.12.2013 03:37

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

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

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

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

Вы хоть представляете, почему, если мы заменим sig_check<func_sig, &T::func_name> на бесплатную проверку функций: sig_check<func_sig, &func_name> не сможет построить с «необъявленным идентификатором», указав имя функции, которую мы хотим проверить? потому что я ожидал, что SFINAE сделает это НЕ ошибкой, он делает это только для членов, почему не для бесплатных функций?

v.oddou 06.03.2015 09:40

Я предполагаю, что это как-то связано с тем фактом, что бесплатная функция не является классом или структурой. Этот метод определения присутствия члена действительно основан на механизме множественного наследования в C++, вынуждая неоднозначность между классом-заглушкой, который существует только с целью размещения члена, который вы проверяете, по сравнению с классом, который вы фактически проверяете для члена дюйм. Это интересный вопрос, не думал об этом. Вы можете проверить другие методы проверки членов C++ 11/14, я видел некоторые умные вещи в новом стандарте.

Brett Rossier 07.03.2015 08:01

Спасибо за ваш ответ, я думаю, мне, возможно, придется более подробно проверить ту информацию, которую вы даете о наследовании, потому что до сих пор я не видел никакой корреляции между простым использованием SFINAE для создания выражения, которое не было бы правильным для выражения доступа к член в параметре типа шаблона и множественное наследование. Но я полностью уверен, что в C++ даже отдаленные концепции могут накладываться друг на друга. Теперь для бесплатных функций этот вопрос интересен: stackoverflow.com/questions/26744589 Ответ T.C, похоже, использует уловку объявления фиктивного элемента, чтобы избежать "необъявленного идентификатора"

v.oddou 09.03.2015 04:39

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

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

С нетерпением жду мнений по этому поводу.

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

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

Johannes Schaub - litb 11.04.2014 21:29

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

Jonathan 26.01.2015 09:22

Ваш первый пример ссылки не работает

NathanOliver 17.09.2015 17:03

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

Gabriel 04.10.2015 17:10

Как вы его используете с enable_if?

zoska 25.01.2017 16:39

Если вы так же озадачены, как и я, двумя аргументами decltype: decltype на самом деле принимает только один; запятая здесь является оператором. См. stackoverflow.com/questions/16044514/…

André 17.02.2017 09:06

Это отлично работает в ситуациях, требующих полных типов, но в ситуациях, когда этого не происходит, это даст ложноотрицательные результаты для неполных (объявленных вперед) типов. Я добавил аналог sfinae_false и использовал тип возврата в переопределении long, который обнаружил наличие деструктора. Это исключало типы, которые все еще были неполными или не имели общедоступных деструкторов. Для меня было приемлемо исключение закрытых деструкторов.

John 22.05.2017 21:23

@Leo: Все ваши примеры ушли - пожалуйста, обновите примеры вставки прямо в stackoverflow и подумайте об обновлении ваших ссылок

Cookie 20.07.2017 21:58

Есть ли причина, по которой вы не использовали бы sizeof(os<<obj), если не встраиваете его в качестве аргумента шаблона или возвращаемого типа, выведенного из std::enable_if? Это кажется эстетическим выбором, но если бы это было так, мне показался бы странным указатель int. @zoska - думаю, это будет auto ... -> typename std::enable_if<sizeof(os << obj), void>::type {...}.

John P 08.09.2017 19:55

@Xeo Это отличный ответ (+1), но в вашем первом примере (serialize) вы не должны использовать типы int и long для определения разрешения перегрузки. Он не компилируется, если int и long имеют одинаковый размер, потому что вызов перегруженной функции (serialize_imp) неоднозначен. Вы можете оставить тип int для первой перегрузки и заменить long на тип wrapper, определенный таким образом: struct wrapper { constexpr wrapper(int a) {++a;} };.

RalphS 07.01.2019 19:42

Как никто не заметил, что это вообще не решение C++ 11? Вывод типа возвращаемого значения - это функция C++ 14.

Peter 11.10.2019 10:00

Почему в этом примере вообще используются потоки? Это действительно необходимо?

einpoklum 03.04.2020 18:08

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

#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 - Adam Nevraumont 02.06.2014 17:40

@Yakk Я думаю, что гипс может решить эту проблему.

user1095108 02.06.2014 23:06

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

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

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

Вы можете улучшить это, чтобы поддерживать функции-члены с аргументами шаблона. Измените template <typename T> на template <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 01.09.2016 20:45

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

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 в своей документации, поэтому я прямо процитирую его:

[...] Hana provides a is_valid function that can be combined with C++14 generic lambdas to obtain a much cleaner implementation of the same thing:

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

This leaves us with a function object has_toString which returns whether the given expression is valid on the argument we pass to it. The result is returned as an IntegralConstant, so constexpr-ness is not an issue here because the result of the function is represented as a type anyway. Now, in addition to being less verbose (that's a one liner!), the intent is much clearer. Other benefits are the fact that has_toString can be passed to higher order algorithms and it can also be defined at function scope, so there is no need to pollute the namespace scope with implementation details.

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.

лаконичнее, чем 03

ZFY 13.03.2020 14:20

@ZFY Я думаю, что Boost.TTI тоже работает с C++ 03, но это наименее элегантное решение из всех.

Morwenn 13.03.2020 17:01

Действительно ли решение C++ 20 действительно? Я бы хотел, но g ++ и msvc отказываются от этого - принимаются только clang.

Bernd 04.04.2020 23:17

в cppreference вы можете прочитать: Если требуемое-выражение содержит недопустимые типы или выражения в своих требованиях, и оно не появляется в объявлении шаблонной сущности, значит, программа сформирована неправильно.

Bernd 04.04.2020 23:36

@BerndBaumanns Правда? Я заставил его работать со стволом GCC: godbolt.org/z/CBwZdE Может быть, вы правы, я только проверил, что он работает, но не проверял, законно ли это в соответствии со стандартной формулировкой.

Morwenn 05.04.2020 14:51

Я тестировал его не с магистралью gcc - просто с версией 9.2, которой он не нравится (см .: godbolt.org/z/_5pr_t). Было бы здорово! Но вы правы - с транком g ++ он просто переводится в логическое значение - но гарантируется ли это стандартом?

Bernd 05.04.2020 18:12

Но он по-прежнему отвергается новейшей версией компилятора MSVC с поддержкой концепций (которая недоступна на godbolt).

Bernd 05.04.2020 18:18

Я знаю, что было предложение (или DR), которое сделало выражения requires глобально используемыми в качестве логических выражений, но оно ортогонально вашему исходному комментарию о том, что программа плохо сформирована - я недостаточно знаю формулировку, чтобы сказать, что такое действительный. GCC 9 реализовал Concepts TS только с -fconcepts, поэтому неудивительно, что он не принимает requires как логическое выражение, поскольку это не разрешено в TS. Если вы правы и код неправильный, я изменю его, чтобы использовать концепцию, две перегрузки и requires.

Morwenn 05.04.2020 19:58

Мне нравится решение инструментария обнаружения (у многих есть доступ к нему через std :: экспериментальный), но я думаю, что его можно улучшить, используя интеграл_constant для обеспечения строгой проверки подписи. шаблон <класс T> с использованием toString_t = std :: integ_constant <std :: string (T :: *) (), & T :: toString>

jisrael18 03.03.2021 20:18

Это решение 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 22.06.2014 23:17

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

Yakk - Adam Nevraumont 22.06.2014 23:22

но вы знаете, если посмотреть с другой стороны ... Мы можем добраться до защищенных членов из класса Derived. Может быть, если поместить все это в класс INSIDE и преобразовать из структур в функции constexpr ...

tower120 22.06.2014 23:38

Вот посмотрите на этот coliru.stacked-crooked.com/a/ee94d16e7c07e093, я просто не могу сделать это constexpr

tower120 22.06.2014 23:48

@ tower120 C++ 1y заставляет его работать: coliru.stacked-crooked.com/a/d8cdfff24a171394

Yakk - Adam Nevraumont 14.07.2014 22:23

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

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

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

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

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

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

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

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

Вот живой пример. Он отлично работает с Clang, но, к сожалению, версии GCC до 5.1 следовали другой интерпретации стандарта 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 04.12.2016 17:31

Да, конечно. Внимательно посмотрите на примеры: вы в основном предоставляете выражение и проверяете, действительно ли оно. Ничто не требует, чтобы это выражение относилось только к вызову функции-члена.

akim 04.12.2016 21:36

N4502 (open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf) - это путь в будущее ... Я искал изящный способ обнаружения вещей по типам, и N4502 - это то, что нужно.

tlonuk 11.03.2017 18:04

Простое решение для 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";
}

Это просто и элегантно, но, строго говоря, не отвечает на вопрос OP: вы не разрешаете вызывающей стороне контрольный для существования функции, вы всегда предоставлять ее. Но все равно приятно.

Adrian W 13.06.2018 20:38

@AdrianW, хорошее замечание. Я обновил свой ответ. Я не тестировал это, хотя

Aaron McDaid 15.06.2018 21:49

В случае, если это поможет кому-то другому, я не смог бы сделать эту работу без template<typename> до вариативной перегрузки: это не рассматривалось для разрешения.

Laboratorio Cobotica 21.12.2018 10:55

Опять же, это недопустимый C++ 11.

Peter 11.10.2019 10:05

Здесь много ответов, но мне не удалось найти версию, которая выполняет упорядочение разрешения метода настоящий, не используя при этом никаких новых функций 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.

Нет, но я сам это объявляю, и он не использует rvalue (посмотрите на верхнюю часть моего кода). Или вы можете просто убедить себя и попробовать живую демонстрацию в режиме C++ 98. PS: static_assert тоже не С ++ 98, но есть обходные пути (живая демонстрация)

user3296587 12.11.2015 21:40

ох! пропустил это. :-)

Ian Ni-Lewis 14.11.2015 00:48

Ваши статические утверждения не работают. Вам нужно использовать размер массива -1 вместо 0 (попробуйте поставить static_assert(false);). Я использовал это в связи с CRTP, где я хочу определить, имеет ли производный класс конкретную функцию, которая, как оказалось, не работает, но ваши утверждения всегда проходили. Я потерял несколько волос из-за этого.

the swine 10.11.2016 22:12

Я предполагаю, что вы используете g ++. Обратите внимание, что у gcc / g ++ есть расширение, которое позволяет использовать массив нулевого размера (gcc.gnu.org/onlinedocs/gcc/Zero-Length.html)

user3296587 07.12.2016 11:36

Не могли бы вы переписать это, чтобы не перегружать оператора? например выбрать другого оператора? Кроме того, избегайте загрязнения пространства имен чем-либо, кроме has_awesome_member?

einpoklum 12.12.2018 20:58

Извините, но это должен быть operator ,, поскольку это единственный оператор, реализованный встроенным типом void (вы можете написать ((void)0, foo()), но любой другой оператор, например, в ((void)0 + foo()), всегда вызывает ошибку компилятора и не может быть отменен), Это означает, что это необходимо для возможности распознавания функций с возвращаемым типом void. - Что касается загрязнения пространства имен: конечно, вы могли бы просто поместить все (кроме operator ,(), который должен оставаться видимым как глобальный оператор) в какое-то пространство имен и настроить has_awesome_member, чтобы просто использовать это пространство имен.

user3296587 27.07.2019 11:15

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

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

Насколько легко это написать, чтобы не зависеть от fit или любой другой библиотеки, кроме стандартной?

einpoklum 12.12.2018 20:57

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

#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 12.07.2017 12:01

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

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
}

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

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

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

Я решил это аналогично ответу "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;
}
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!";
}

«Нам не нужны описания ответов» ... пожалуйста, добавьте информативное описание к своему ответу, чтобы улучшить его. Спасибо.

YesThatIsMyName 25.02.2019 16:36

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

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

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

////////////////////////////////////////////
// is_valid implementation
////////////////////////////////////////////

#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){} )

////////////////////////////////////////////
// Example
////////////////////////////////////////////

#include <iostream>
#include <string>

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), 
                  "Example class must have Foo member");
    static_assert(IS_VALID(Example, Bar()), 
                  "Example class must have Bar() member function");
    static_assert(!IS_VALID(Example, ZFoo), 
                  "Example class must not have ZFoo member.");
    static_assert(!IS_VALID(Example, ZBar()), 
                  "Example class must not have ZBar() member function");

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

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

template<typename Callable, typename... Args, typename = decltype(declval<Callable>()(declval<Args>()...))>
std::true_type isCallableImpl(Callable, Args...) { return {}; }

std::false_type isCallableImpl(...) { return {}; }

template<typename... Args, typename Callable>
constexpr bool isCallable(Callable callable) {
    return decltype(isCallableImpl(callable, declval<Args>()...)){};
}

Использование:

constexpr auto TO_STRING_TEST = [](auto in) -> decltype(in.toString()) { return {}; };
constexpr bool TO_STRING_WORKS = isCallable<T>(TO_STRING_TEST);

Я знаю, что этому вопросу много лет, но я думаю, что для таких людей, как я, было бы полезно получить более полный обновленный ответ, который также работает для перегруженных методов const, таких как std::vector<>::begin.

На основании этого отвечать и отвечать из моего следующего вопроса, вот более полный ответ. Обратите внимание, что это будет работать только с C++ 11 и выше.

#include <iostream>
#include <vector>

class EmptyClass{};

template <typename T>
class has_begin
{
    private:
    has_begin() = delete;
    
    struct one { char x[1]; };
    struct two { char x[2]; };

    template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * ) ;
    template <typename C> static two test(...);    

public:
    static constexpr bool value = sizeof(test<T>(0)) == sizeof(one);
};
    
int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass::begin() exists: " << has_begin<EmptyClass>::value << std::endl;
    return 0;
}

Или более короткая версия:

#include <iostream>
#include <vector>

class EmptyClass{};

template <typename T, typename = void>
struct has_begin : std::false_type {};

template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};

int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass exists: " << has_begin<EmptyClass>::value << std::endl;
}

Обратите внимание, что здесь должен быть предоставлен полный пример вызова. Это означает, что если бы мы проверили существование метода resize, то мы бы поставили resize(0).

Объяснение глубокой магии:

Первый ответ на этот вопрос использовал test( decltype(&C::helloworld) ); однако это проблематично, когда метод, который он тестирует, неоднозначен из-за перегрузки const, что приводит к неудачной попытке замены.

Чтобы решить эту неоднозначность, мы используем оператор void, который может принимать любые параметры, потому что он всегда транслируется в noop, и, таким образом, неоднозначность сводится к нулю, и вызов действителен, пока существует метод:

has_begin<T, decltype(void(std::declval<T &>().begin()))>

Вот что происходит по порядку: Мы используем std::declval<T &>() для создания вызываемого значения, для которого затем может быть вызван begin. После этого значение begin передается как параметр в оператор void. Затем мы извлекаем тип этого выражения void с помощью встроенного decltype, чтобы его можно было использовать в качестве аргумента типа шаблона. Если begin не существует, то подстановка недействительна, и согласно SFINAE вместо нее используется другое объявление.

Возможно, не так хорошо, как другие примеры, но это то, что я придумал для C++ 11. Это работает для выбора перегруженных методов.

template <typename... Args>
struct Pack {};

#define Proxy(T) ((T &)(*(int *)(nullptr)))

template <typename Class, typename ArgPack, typename = nullptr_t>
struct HasFoo
{
    enum { value = false };
};

template <typename Class, typename... Args>
struct HasFoo<
    Class,
    Pack<Args...>,
    decltype((void)(Proxy(Class).foo(Proxy(Args)...)), nullptr)>
{
    enum { value = true };
};

Пример использования

struct Object
{
    int foo(int n)         { return n; }
#if SOME_CONDITION
    int foo(int n, char c) { return n + c; }
#endif
};

template <bool has_foo_int_char>
struct Dispatcher;

template <>
struct Dispatcher<false>
{
    template <typename Object>
    static int exec(Object &object, int n, char c)
    {
        return object.foo(n) + c;
    }
};

template <>
struct Dispatcher<true>
{
    template <typename Object>
    static int exec(Object &object, int n, char c)
    {
        return object.foo(n, c);
    }
};

int runExample()
{
    using Args = Pack<int, char>;
    enum { has_overload = HasFoo<Object, Args>::value };
    Object object;
    return Dispatcher<has_overload>::exec(object, 100, 'a');
}

Я искал метод, который позволяет как-то не связывать имя структуры has_member с именем члена класса. На самом деле, это было бы проще, если бы лямбда могла быть разрешена в неоцененном выражении (это запрещено стандартом), то есть has_member<ClassName, SOME_MACRO_WITH_DECLTYPE(member_name)>

#include <iostream>
#include <list>
#include <type_traits>

#define LAMBDA_FOR_MEMBER_NAME(NAME) [](auto object_instance) -> decltype(&(decltype(object_instance)::NAME)) {}

template<typename T>
struct TypeGetter
{
    constexpr TypeGetter() = default;
    constexpr TypeGetter(T) {}
    using type = T;

    constexpr auto getValue()
    {
        return std::declval<type>();
    }
};

template<typename T, typename LambdaExpressionT>
struct has_member {
    using lambda_prototype = LambdaExpressionT;

    //SFINAE
    template<class ValueT, class = void>
    struct is_void_t_deducable : std::false_type {};

    template<class ValueT>
    struct is_void_t_deducable<ValueT,
        std::void_t<decltype(std::declval<lambda_prototype>()(std::declval<ValueT>()))>> : std::true_type {};

    static constexpr bool value = is_void_t_deducable<T>::value;
};

struct SimpleClass
{
    int field;
    void method() {}
};

int main(void)
{   
    const auto helpful_lambda = LAMBDA_FOR_MEMBER_NAME(field);
    using member_field = decltype(helpful_lambda);
    std::cout << has_member<SimpleClass, member_field>::value;

    const auto lambda = LAMBDA_FOR_MEMBER_NAME(method);
    using member_method = decltype(lambda);
    std::cout << has_member<SimpleClass, member_method>::value;
    
}

Другие вопросы по теме