Проверьте, имеет ли класс функцию-член заданной подписи - PullRequest
119 голосов
/ 18 сентября 2008

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

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

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

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

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

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

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

Ответы [ 14 ]

113 голосов
/ 30 мая 2013

Вот возможная реализация, основанная на возможностях 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
86 голосов
/ 18 сентября 2008

Я не уверен, правильно ли я вас понимаю, но вы можете использовать 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>());
}
35 голосов
/ 22 мая 2012

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

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

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

12 голосов
/ 01 июня 2013

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

Проверка для члена 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;

OR

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)
11 голосов
/ 18 сентября 2008

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

Вот более простой ответ на вопрос Майка Кингхана. Это обнаружит унаследованные методы. Он также проверит точную подпись (в отличие от подхода 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, "");

Runnable пример

4 голосов
/ 09 мая 2016

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

  1. Перегрузка шаблона функции с различными типами возврата в зависимости от того, доступен ли метод
  2. В соответствии с мета-условиями в заголовке type_traits мы хотим вернуть true_type или false_type от наших перегрузок
  3. Объявите перегрузку true_type, ожидающую использования переменных Variadic, 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 для целей встроенного кода или метапрограммирования.

4 голосов
/ 28 марта 2013

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

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

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

#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)
}
4 голосов
/ 07 ноября 2012

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

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

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
3 голосов
/ 18 сентября 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...