Проверьте, имеет ли класс функцию-член заданной сигнатуры.

StackOverflow https://stackoverflow.com/questions/87372

  •  01-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

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

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

Это было полезно?

Решение

Я не уверен, правильно ли я вас понял, но вы можете использовать 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++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

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

#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, используя «The Sizeof () трюк», для обнаружения лишь того, независимо от того, T имеет некоторую функцию-член mf (см., например,этот ответ и комментарии).Но установить это T::mf существует, часто (обычно?) недостаточно хорош:Вам также может потребоваться установить, что у него есть желаемая подпись.Вот где иллюстрированная техника оценивает.Указанный вариант желаемой подписи вписан в параметр типа шаблона, который должен быть удовлетворен&T::mf чтобы зонд SFINAE увенчался успехом.Но этот шаблон, созданный в сиянии, дает неправильный ответ, когда T::mf передается по наследству.

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

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

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

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

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

Проверить наличие участника x в данном классе.Это может быть var, func, class, Union или перечисление:

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

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

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

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

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

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

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

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

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

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

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

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

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

ИЛИ

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

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

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

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

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

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

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

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

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

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

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

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

CREATE_MEMBER_CHECK:

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

CREATE_MEMBER_VAR_CHECK:

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

CREATE_MEMBER_FUNC_SIG_CHECK:

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

CREATE_MEMBER_CLASS_CHECK:

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

CREATE_MEMBER_UNION_CHECK:

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

CREATE_MEMBER_ENUM_CHECK:

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

CREATE_MEMBER_FUNC_CHECK:

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

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)

Этого должно быть достаточно, если вы знаете имя ожидаемой функции-члена.(В этом случае функция 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);
}

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

работоспособный пример

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

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

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

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

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

Нашел еще один нить предлагая что-то подобное, основываясь на УВЕЛИЧИТЬ обсуждение.Вот обобщение предлагаемого решения в виде объявления двух макросов для класса признаков, следуя модели повышение::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)
}

Для этого нам нужно будет использовать:

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

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

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

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

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

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

Без поддержки 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 быть особенным, унаследовавшим члена, которого мы ищем.

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

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

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

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

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

Я считаю, что ответ, который вы ищете, находится здесь.

http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time

и немного более заполненный пример здесь

http://pastie.org/298994

Я использую эту технику для обнаружения присутствия поддерживающей оператор ostream << для рассматриваемого класса, а затем в зависимости от него генерировать другой фрагмент кода.

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

Брэд

Если вы используете Facebook folly, у них есть готовый макрос, который поможет вам:

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top