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

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

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

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

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

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

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

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

Аналогичный вопрос: stackoverflow.com/questions/257288

Johannes Schaub - litb 26.08.2009 00:02

@ R.MartinhoFernandes Какой ответ вы ищете? Этот ответ Майка Кингана идет довольно подробно и использует материал C++ 11.

jrok 29.05.2013 21:04

@ R.MartinhoFernandes Может быть, это - это современная версия, которую вы ищете?

Daniel Frey 29.05.2013 23:36
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
143
3
88 219
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

Чтобы не вмешиваться, вы также можете поместить serialize в пространство имен сериализуемого класса или класса архива благодаря Поиск по Кенигу. Подробнее см. Пространства имен для переопределения бесплатных функций. :-)

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

Хорошо. Вторая попытка. Ничего страшного, если и этот тебе не понравится, ищу еще идеи.

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

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

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

ugasoft 18.09.2008 01:34

Моим третьим решением было бы использовать SFINAE. Поскольку ответ yrp уже упоминает об этом, я не буду вдаваться в подробности (потому что я все еще исследую его: я знаю идею, но дьявол кроется в деталях), если его решение не сработает для вас в конце . :-)

Chris Jester-Young 18.09.2008 02:00
Ответ принят как подходящий

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

template<typename T>
struct HasUsedMemoryMethod
{
    template<typename U, size_t (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
    template<typename U> static int Test(...);
    static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
        // We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
    ReportMemUsage(m, 
        std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}

Что это за хрень??? это законный код C++ ?? вы можете написать "template <typename U, size_t (U :: *) () const>" ?? но ... это отличное и новое решение! Благодарю, завтра лучше проанализирую с коллегами ... отлично!

ugasoft 18.09.2008 01:39

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

Richard Corden 18.09.2008 21:50

Простое определение int_to_type может быть таким: 'template <int N> struct int_to_type {};'. Многие реализации сохраняют значение параметра N либо в перечислении, либо в статической целочисленной константе (template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})

David Rodríguez - dribeas 04.09.2009 02:04

При использовании C++ 0x вы можете просто вернуть нужный тип и написать «decltype (Test <T> (0))» всякий раз, когда вам нужен этот тип.

tstenner 09.09.2009 18:14

Просто возьмите boost :: integration_constant вместо int_to_type.

Vadim Ferderer 28.09.2009 09:15

Что означает U :: * точно. Может кто-нибудь указать на стандарт?

Johan Lundberg 19.04.2015 21:48

@JohanLundberg Это указатель на (нестатическую) функцию-член. Например, size_t(std::vector::*p)() = &std::vector::size;.

Kuba hasn't forgotten Monica 27.07.2015 17:28

Можно ли проверить имя функции передачи как второй параметр шаблона, то есть HasMethod<Type,method_signature>?

kyb 03.08.2017 12:00

Это не работает для меня, потому что компилятор предпочитает параметр Test (...), даже если другой тоже будет работать: /. Т.е. он не работает так, как есть, но когда я закомментировал строку Test (...), он работает (если метод присутствует). Как я могу сделать тест (...) по-настоящему всеобъемлющим ПОСЛЕ SFINEA?

Carlo Wood 16.12.2018 07:41

О, я понял, почему это не удается .. мой метод закрытый: /

Carlo Wood 16.12.2018 08:03

В случае защищенных методов требуется шаблон доступа, производный от класса вопроса. В случае частного метода это не сработает

Swift - Friday Pie 26.11.2019 14:58

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

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

спасибо! это похоже на решение, предложенное yrp. Я не знал, что шаблон можно использовать для функций-членов. Это новая функция, которую я узнал сегодня! ... и новый урок: «никогда не говори, что ты специалист по C++» :)

ugasoft 18.09.2008 01:43

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

#include <type_traits>
#include <iostream>
#include <memory>

/*  Here we apply the accepted answer's technique to probe for the
    the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
    template<typename U, E (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::operator*>*);
    template<typename U> static int Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

using namespace std;

/* Here we test the `std::` smart pointer templates, including the
    deprecated `auto_ptr<T>`, to determine in each case whether
    T = (the template instantiated for `int`) provides 
    `int & T::operator*() const` - which all of them in fact do.
*/ 
int main(void)
{
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
    return 0;
}

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

Если вы еще не разбираетесь в этой проблеме, взгляните на определение std::shared_ptr<T> в заголовке <memory> прольет свет. В этом реализация, std::shared_ptr<T> является производным от базового класса от которого он наследует operator*() const. Итак, создание шаблона SFINAE<U, &U::operator*>, который представляет собой "поиск" оператора для U = std::shared_ptr<T> не будет, потому что std::shared_ptr<T> не имеет operator*() сам по себе, а создание шаблона не "сделать наследование".

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

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

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

#include <type_traits>

/*! The template `has_const_reference_op<T,E>` exports a
    boolean constant `value that is true iff `T` provides
    `E T::operator*() const`
*/ 
template< typename T, typename E>
struct has_const_reference_op
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A>
    static std::true_type test(E (A::*)() const) {
        return std::true_type();
    }

    /* SFINAE operator-exists :) */
    template <typename A> 
    static decltype(test(&A::operator*)) 
    test(decltype(&A::operator*),void *) {
        /* Operator exists. What about sig? */
        typedef decltype(test(&A::operator*)) return_type; 
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type(); 
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0,0)) type;

    static const bool value = type::value; /* Which is it? */
};

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

Нам нужно исследовать по крайней мере одну и максимум две точки информации:

  • T::operator*() существует вообще? Если нет, то все готово.
  • Учитывая, что T::operator*() существует, это его подпись E T::operator*() const?

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

    typedef decltype(test<T>(0,0)) type;

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

Почему мы проходим двоих? Просто заставить разрешение исключить /* SFINAE operator-has-correct-sig :) */. Второй аргумент не имеет другого значения.

Этот вызов test(0,0) разрешит только /* SFINAE operator-exists :) */. в случае, если первый аргумент 0 соответствует первому типу параметра этой перегрузки, что decltype(&A::operator*), с A = T. 0 удовлетворит этот тип на всякий случай T::operator* существует.

Предположим, что компилятор сказал на это «да». Тогда это происходит с /* SFINAE operator-exists :) */, и ему необходимо определить тип возврата вызов функции, в данном случае decltype(test(&A::operator*)) - тип возврата еще одного вызова test().

На этот раз мы передаем только один аргумент &A::operator*, который теперь знаю, существует, иначе нас бы здесь не было. Звонок в test(&A::operator*) может решите либо /* SFINAE operator-has-correct-sig :) */, либо снова может разрешиться до /* SFINAE game over :( */. Звонок будет соответствовать /* SFINAE operator-has-correct-sig :) */ на всякий случай &A::operator* удовлетворяет тип с одним параметром этой перегрузки, то есть E (A::*)() const, с A = T.

Компилятор скажет здесь Да, если T::operator* имеет желаемую подпись, а затем снова необходимо оценить возвращаемый тип перегрузки. Больше не надо "рекурсии" сейчас: это std::true_type.

Если компилятор не выбирает /* SFINAE operator-exists :) */ для звоните test(0,0) или не выбирает /* SFINAE operator-has-correct-sig :) */ для вызова test(&A::operator*), то в любом случае он идет с /* SFINAE game over :( */, и последний тип возвращаемого значения - std::false_type.

Вот тестовая программа, которая показывает шаблон, производящий ожидаемый ответы в различных выборках случаев (снова GCC 4.6.3).

// To test
struct empty{};

// To test 
struct int_ref
{
    int & operator*() const {
        return *_pint;
    }
    int & foo() const {
        return *_pint;
    }
    int * _pint;
};

// To test 
struct sub_int_ref : int_ref{};

// To test 
template<typename E>
struct ee_ref
{
    E & operator*() {
        return *_pe;
    }
    E & foo() const {
        return *_pe;
    }
    E * _pe;
};

// To test 
struct sub_ee_ref : ee_ref<char>{};

using namespace std;

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    cout << "Expect Yes" << endl;
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value;
    cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
    cout << has_const_reference_op<std::vector<int>::const_iterator,
            int const &>::value;
    cout << has_const_reference_op<int_ref,int &>::value;
    cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
    cout << "Expect No" << endl;
    cout << has_const_reference_op<int *,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,char &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int>::value;
    cout << has_const_reference_op<unique_ptr<long>,int &>::value;
    cout << has_const_reference_op<int,int>::value;
    cout << has_const_reference_op<std::vector<int>,int &>::value;
    cout << has_const_reference_op<ee_ref<int>,int &>::value;
    cout << has_const_reference_op<sub_ee_ref,int &>::value;
    cout << has_const_reference_op<empty,int &>::value  << endl;
    return 0;
}

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

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

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;

Не будет ли &A::foo ошибкой компиляции, если foo вообще отсутствует в A? Я прочитал исходный вопрос как предполагаемый для работы с любым входным классом, а не только с теми, у которых есть какой-то член с именем foo.

Jeff Walden 12.03.2013 02:11

Это не работает. Выдает ошибку компиляции, если функция не является членом A.

Marc Dirven 28.09.2020 22:42

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

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

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

#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>

/// Has constant function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)

/// Has non-const function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)

// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...)  \
    template                                                                  \
    <   typename Type,                                                        \
        bool is_class = boost::is_class<Type>::value                          \
    >                                                                         \
    class has_func_ ## func_name;                                             \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,false>                                  \
    {public:                                                                  \
        BOOST_STATIC_CONSTANT( bool, value = false );                         \
        typedef boost::false_type type;                                       \
    };                                                                        \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,true>                                   \
    {   struct yes { char _foo; };                                            \
        struct no { yes _foo[2]; };                                           \
        struct Fallback                                                       \
        {   func_ret_type func_name( __VA_ARGS__ )                            \
                UTILITY_OPTIONAL(func_const,const) {}                         \
        };                                                                    \
        struct Derived : public Type, public Fallback {};                     \
        template <typename T, T t>  class Helper{};                           \
        template <typename U>                                                 \
        static no deduce(U*, Helper                                           \
            <   func_ret_type (Fallback::*)( __VA_ARGS__ )                    \
                    UTILITY_OPTIONAL(func_const,const),                       \
                &U::func_name                                                 \
            >* = 0                                                            \
        );                                                                    \
        static yes deduce(...);                                               \
    public:                                                                   \
        BOOST_STATIC_CONSTANT(                                                \
            bool,                                                             \
            value = sizeof(yes)                                               \
                == sizeof( deduce( static_cast<Derived*>(0) ) )               \
        );                                                                    \
        typedef ::boost::integral_constant<bool,value> type;                  \
        BOOST_STATIC_CONSTANT(bool, is_const = func_const);                   \
        typedef func_ret_type return_type;                                    \
        typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;                \
    }

// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__

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

template<class T>
class has_func_[func_name]
{
public:
    /// Function definition result value
    /** Tells if the tested function is defined for type T or not.
    */
    static const bool value = true | false;

    /// Function definition result type
    /** Type representing the value attribute usable in
        http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
    */
    typedef boost::integral_constant<bool,value> type;

    /// Tested function constness indicator
    /** Indicates if the tested function is const or not.
        This value is not deduced, it is forced depending
        on the user call to one of the traits generators.
    */
    static const bool is_const = true | false;

    /// Tested function return type
    /** Indicates the return type of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef func_ret_type return_type;

    /// Tested function arguments types
    /** Indicates the arguments types of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};

Итак, каково типичное использование этого?

// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
    // Next line will declare the traits class
    // to detect the member function void foo(int,int) const
    DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}

// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>

// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   _this_.foo(a,b);
}

// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   default_foo(_this_,a,b);
}

// Let us declare test types
struct empty
{
};
struct direct_foo
{
    void foo(int,int);
};
struct direct_const_foo
{
    void foo(int,int) const;
};
struct inherited_const_foo :
    public direct_const_foo
{
};

// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
    int a;
    foo_bar(a); // calls default_foo

    empty b;
    foo_bar(b); // calls default_foo

    direct_foo c;
    foo_bar(c); // calls default_foo (member function is not const)

    direct_const_foo d;
    foo_bar(d); // calls d.foo (member function is const)

    inherited_const_foo e;
    foo_bar(e); // calls e.foo (inherited member function)
}

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

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

#include <type_traits>

// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.

template<typename, typename T>
struct has_serialize {
    static_assert(
        std::integral_constant<T, false>::value,
        "Second template parameter needs to be of function type.");
};

// specialization that does the checking

template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
    template<typename T>
    static constexpr auto check(T*)
    -> typename
        std::is_same<
            decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
            Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        >::type;  // attempt to call it and see if the return type is correct

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(0)) type;

public:
    static constexpr bool value = type::value;
};

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

struct X {
     int serialize(const std::string&) { return 42; } 
};

struct Y : X {};

std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1

Работает ли это, если у Y нет метода под названием «сериализация»? Я не понимаю, как он вернет ложное значение, если метод «сериализации» не существует.

Talia 20.02.2014 07:47

@Collin в этом случае замена параметра шаблона не выполняется при первой перегрузке проверки и отбрасывается из набора перегрузки. Он возвращается ко второму, который возвращает false_type. Это не ошибка компилятора, потому что принцип SFINAE.

jrok 20.02.2014 11:57

О, и стрелка из нового синтаксиса возвращаемого значения в C++ 11, описанного здесь: cprogramming.com/c++11/…

Talia 22.02.2014 04:24

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

elios264 16.10.2015 18:19

@ elios264 Нет. Вы можете использовать макрос, чтобы написать шаблон для каждой функции, которую вы хотите проверить.

jrok 16.10.2015 19:37

Какая-либо конкретная причина, по которой аргумент для проверки имеет тип T *, а не T или T &?

shibumi 09.11.2017 08:28

Я понял это. Без типа указателя сложно передать аргумент шаблона.

shibumi 09.11.2017 08:42

Но что, если serialize сам принимает шаблон. Есть ли способ проверить существование serialize, не вводя точный тип?

Hi-Angel 09.05.2018 15:40

Я думаю, что это можно упростить как stackoverflow.com/a/60603142/3676427

Isaac Pascual 09.03.2020 17:57

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

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

Это здорово; было бы неплохо поместить это в одну библиотеку заголовочного файла.

Allan 07.10.2019 19:06

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

template <class C>
class HasGreetMethod
{
    template <class T>
    static std::true_type testSignature(void (T::*)(const char*) const);

    template <class T>
    static decltype(testSignature(&T::greet)) test(std::nullptr_t);

    template <class T>
    static std::false_type test(...);

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");

Работает пример

Это хорошо, но не сработает, если функция не принимает аргументов.

Triskeldeian 10.05.2016 20:27

Он отлично работает. У меня не возникло проблем с применением этого трюка к функциям-членам, не имеющим аргументов.

JohnB 27.11.2016 19:45

Это хорошо работает для меня с несколькими аргументами методов и без них, в том числе с перегрузками, в том числе с наследованием, а также с использованием using для переноса перегрузок из базового класса. У меня это работает на MSVC 2015 и с Clang-CL. Однако он не работает с MSVC 2012.

steveire 03.03.2017 14:37

Для этого нам понадобится:

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

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

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

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

У этого решения есть 2 недостатка:

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

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

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

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

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

Последующий вызов details::test_int<a>::value или details::test_void<a>::value приведет к получению true или false для целей встроенного кода или метапрограммирования.

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

SSCCE

#include <iostream>
using namespace std;

struct A { void foo(void); };
struct Aa: public A { };
struct B { };

struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };

template<typename T>
struct FooFinder {
    typedef char true_type[1];
    typedef char false_type[2];

    template<int>
    struct TypeSink;

    template<class U>
    static true_type &match(U);

    template<class U>
    static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);

    template<class U>
    static false_type &test(...);

    enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};

int main() {
    cout << FooFinder<A>::value << endl;
    cout << FooFinder<Aa>::value << endl;
    cout << FooFinder<B>::value << endl;

    cout << FooFinder<retA>::value << endl;
    cout << FooFinder<argA>::value << endl;
    cout << FooFinder<constA>::value << endl;
    cout << FooFinder<varA>::value << endl;
}

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

A, Aa и B - это рассматриваемые классы, причем Aa является специальным классом, который наследует член, который мы ищем.

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

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

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

Обе функции test вместе с объявлением enum в конечном итоге образуют центральный шаблон SFINAE. Существует общий, использующий многоточие, который возвращает false_type, и аналог с более конкретными аргументами, которые имеют приоритет.

Чтобы иметь возможность создать экземпляр функции test с аргументом шаблона T, необходимо создать экземпляр функции match, поскольку ее возвращаемый тип требуется для создания экземпляра аргумента TypeSink. Предостережение заключается в том, что &U::foo, заключенный в аргумент функции, является нет, на который ссылается специализация аргументов шаблона, поэтому поиск унаследованных членов все еще выполняется.

Если вы используете глупость facebook, их макрос готов из коробки, чтобы помочь вам:

#include <folly/Traits.h>
namespace {
  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace

void some_func() {
  cout << "Does class Foo have a member int test() const? "
    << boolalpha << has_test_traits<Foo, int() const>::value;
}

Хотя детали реализации такие же, как и в предыдущем ответе, использовать библиотеку проще.

У меня была аналогичная потребность, и я наткнулся на это ТАК. Здесь предлагается много интересных / мощных решений, хотя это немного длинно для конкретной потребности: определить, есть ли у класса функция-член с точной сигнатурой. Итак, я немного прочитал / протестировал и придумал свою версию, которая могла бы заинтересовать. Он обнаруживает:

  • статическая функция-член
  • нестатическая функция-член
  • нестатическая функция-член const

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

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

Выход :

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1

Похоже, вам нужна идиома детектора. Приведенные выше ответы являются вариациями на этот счет, которые работают с C++ 11 или C++ 14.

Библиотека std::experimental имеет функции, которые, по сути, делают это. Перерабатывая пример сверху, это может быть:

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>().serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

Если вы не можете использовать std :: экспериментальный, элементарную версию можно сделать следующим образом:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>().serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

Поскольку has_serialize_t на самом деле является либо std :: true_type, либо std :: false_type, его можно использовать с помощью любой из распространенных идиом SFINAE:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

Или с помощью отправки с разрешением перегрузки:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}

Для современного C++ это лучший ответ.

cmannett85 13.02.2021 14:33

Это мой любимый ответ, но это решение (как и многие другие) проверяет только возможность вызова метода с заданным типом. Чтобы гарантировать точное совпадение подписи, используйте шаблон integ_constant как Op. template <class T> с использованием serialize_method_t = std :: integration_constant <void (T :: *) (int), & T :: serialize>

jisrael18 03.03.2021 20:08

Основываясь на отвечатьрывок, я избегал использования вложенных классов шаблонов и / или функций.

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

Мы можем использовать указанные выше макросы, как показано ниже:

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

Предложения приветствуются.

С С ++ 20 это становится намного проще. Скажем, мы хотим проверить, имеет ли класс T функцию-член void T::resize(typename T::size_type). Например, std::vector<U> имеет такую ​​функцию-член. Потом,

template<typename T>
concept has_resize_member_func = requires {
    typename T::size_type;
    { std::declval<T>().resize(std::declval<typename T::size_type>()) } -> std::same_as<void>;
};

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

static_assert(has_resize_member_func<std::string>, "");
static_assert(has_resize_member_func<int> == false, "");

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