Перевірте, чи клас має функцію-член для даної підпису


135

Я прошу шаблонний трюк, щоб визначити, чи клас має певну функцію члена даної підпису.

Проблема схожа на ту, що цитується тут http://www.gotw.ca/gotw/071.htm але не та сама: у статті книги Саттера він відповів на запитання, що клас C ОБОВ'ЯЗКОВО БЕЗПЕЧИТИ функцію члена з певний підпис, інакше програма не збирається. У своїй проблемі мені потрібно щось зробити, якщо клас виконує цю функцію, інакше робити "щось інше".

З подібною проблемою зіткнулися і boost :: serialization, але мені не подобається прийняте ними рішення: функція шаблону, яка за замовчуванням викликає вільну функцію (яку потрібно визначити) з певним підписом, якщо ви не визначите певну функцію члена ( у їхньому випадку "серіалізувати", який приймає 2 параметри заданого типу) з певним підписом, інакше трапиться помилка компіляції. Тобто здійснювати як нав'язливу, так і не нав'язливу серіалізацію.

Мені це рішення не подобається з двох причин:

  1. Щоб не бути нав'язливим, ви повинні перекрити глобальну функцію "serialize", яка знаходиться в boost :: просторі імен серіалізації, тому у вас є КОД КЛІЄНТА, щоб відкрити збільшення простору імен і серіалізацію простору імен!
  2. Стек для вирішення цього безладу складав від 10 до 12 викликів функцій.

Мені потрібно визначити користувацьку поведінку для класів, які не мають цієї функції-члена, і мої сутності знаходяться в різних просторах імен (і я не хочу переосмислювати глобальну функцію, визначену в одному просторі імен, поки я перебуваю в іншому)

Чи можете ви дати мені підказку, щоб вирішити цю загадку?



@ R.MartinhoFernandes Яку відповідь ви шукаєте? Ця відповідь Майка Кінгана досить глибока і використовує матеріали C ++ 11.
jrok

@ R.MartinhoFernandes Можливо, це сучасна версія, яку ви шукаєте?
Даніель Фрей

Відповіді:


90

Я не впевнений, чи правильно я вас розумію, але ви можете використовувати 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>());
}

14
wtf це ??? це законний код C ++ ?? чи можете ви написати "шаблон <typename U, size_t (U :: *) () const>" ?? але ... це чудове і нове рішення! Дякую, я краще проаналізую завтра зі своїми колегами ... чудово!
ugasoft

2
У прикладі відсутнє визначення 'int_to_type'. Очевидно, це не додає відповіді, але це означає, що люди можуть побачити ваш код у дії після швидкого вирізання та вставки.
Річард Корден

2
Простим визначенням int_to_type може бути: 'шаблон <int N> struct int_to_type {};'. Багато реалізацій зберігають значення параметра N або enum або інше в статичній цілій константі (шаблон <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
Девід Родрігес - дрибес

2
Просто прийміть boost :: integral_constant замість int_to_type.
Вадим Фердерер

2
@JohanLundberg Це функція вказівника на (нестатичний) член. Наприклад, size_t(std::vector::*p)() = &std::vector::size;.
Відновіть Моніку

133

Ось можлива реалізація, спираючись на функції 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 не має методу, який називається "серіалізувати"? Я не бачу, як це поверне помилкове значення, якби метод "серіалізації" не існував.
Колін

1
@Collin у такому випадку заміна параметра шаблону не вдається для першої перевантаження перевірки, і він відміняється від набору перевантажень. Він повертається до другого, який повертає false_type. Це не помилка компілятора, оскільки принцип SFINAE.
jrok

1
@ elios264 Немає Ви можете використовувати макрос для написання шаблону для кожної функції, яку ви хочете перевірити.
jrok

1
Будь-яка конкретна причина, чому аргумент для перевірки має тип T *, а не T або T &?
shibumi

1
Але що робити, якщо serializeсам приймає шаблон. Чи є спосіб перевірити serializeнаявність без введення точного типу?
Привіт-Ангел

37

Прийнята відповідь на це запитання про інтроспекцію членів функції компіляції, хоч і є просто популярною, має затяжку, яку можна спостерігати в наступній програмі:

#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, використовуючи "Tirke 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;
}

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


16

Ось кілька фрагментів використання: * Кишки для цього все далі

Перевірте члена xв даному класі. Це може бути var, func, class, Union або enum:

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

Перевірте функцію члена void x():

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

Перевірка змінної члена x:

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

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

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

Перевірте членство в союзі x:

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

Перевірте кількість членів x:

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

Перевірте наявність будь-якої функції члена x незалежно від підпису:

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

АБО

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

Деталі та суть:

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

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

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

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

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

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

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

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

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

Макроси (El Diablo!):

CREATE_MEMBER_CHECK:

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

CREATE_MEMBER_VAR_CHECK:

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

CREATE_MEMBER_FUNC_SIG_CHECK:

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

CREATE_MEMBER_CLASS_CHECK:

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

CREATE_MEMBER_UNION_CHECK:

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

CREATE_MEMBER_ENUM_CHECK:

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

CREATE_MEMBER_FUNC_CHECK:

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

CREATE_MEMBER_CHECKS:

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

1
Це чудово; було б непогано помістити це в єдину бібліотеку файлів заголовків.
Аллан

12

Цього має бути достатньо, якщо ви знаєте назву функції-члена, яку ви очікуєте. . подібний до 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);
}

4
тхаки! це схоже на рішення, запропоноване yrp. Я не знав, що шаблон може бути розміщений на функції членів. Це нова особливість, яку я дізнався сьогодні! ... і новий урок: "ніколи не кажи, що ти
знаєш

7

Ось простіше взяти на себе відповідь Майка Кінгана. Це дозволить виявити успадковані методи. Він також перевіряє наявність точної підпису ( в відміну від підходу Джрок, який дозволяє перетворення аргументів).

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

Це чудово працює. Я не мав жодних проблем із застосуванням цього фокусу до функцій членів, не беручи аргументів.
JohnB

Це добре працює для мене з безліччю аргументів методу і без методів, в тому числі з перевантаженнями, в тому числі з успадкуванням, і з використанням usingдля приведення перевантажень з базового класу. Він працює для мене на MSVC 2015 та з Clang-CL. Однак він не працює з MSVC 2012.
steveire

5

Ви можете використовувати std :: is_member_function_pointer

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

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

16
Чи не &A::fooбуде помилка компіляції , якщо їсти не fooна всіх в A? Я читав оригінальне запитання як те, що він повинен працювати з будь-яким класом введення, а не тільки з тими, хто має назву якогось члена foo.
Джефф Уолден

5

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

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

5

Для цього нам потрібно використовувати:

  1. Перевантаження шаблону функцій із різними типами повернення залежно від того, чи доступний метод
  2. Відповідно до метаумов у type_traitsзаголовку, ми хочемо повернути a 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для цілей вбудованого коду чи метапрограмування.


3

Щоб бути не нав'язливим, ви також можете помістити serializeв простір імен класу, що серіалізується, або класу архіву, завдяки пошуку Koenig . Докладніше див. У просторах імен для заміщення вільних функцій . :-)

Відкриття будь-якого даного простору імен для реалізації вільної функції - просто неправильно. (наприклад, вам не слід відкривати простір імен stdдля реалізації swapдля власних типів, але слід використовувати пошук Koenig.)


3

Ви, схоже, хочете ідіому детектора. Наведені вище відповіді - це варіанти, які працюють із 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 :: eksperimental, рудиментарну версію можна зробити так:

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

2

Добре. Друга спроба. Це добре, якщо вам це теж не подобається, я шукаю ще ідеї.

Стаття Герба Саттера розповідає про риси. Таким чином, у вас може бути клас ознак, екземпляр яких за замовчуванням має резервну поведінку, а для кожного класу, де існує функція вашого члена, тоді клас ознак спеціалізується для виклику функції члена. Я вважаю, що стаття Герба згадує техніку зробити це, щоб вона не передбачала багато копіювання та вклеювання.

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


е ... я проаналізував це рішення ... Я думаю, що це трохи надто дорого для користувачів моєї системи. (добре, я визнаю, я розробляю потокову структуру і вибираю між розширенням iostream або переписанням чогось легшого)
ugasoft

Моє третє рішення - використовувати SFINAE. Оскільки відповідь yrp вже згадує про це, я не вдаваюся в нього (тому що я все ще досліджую його: я знаю ідею, але чорт у деталях), якщо, врешті-решт, його рішення не допоможе тобі. . :-)
Кріс Єстер-Янг

1

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

В і є заміною для кореспондентських C ++ 11 класів. Також для розуміння мета-програмування шаблонів вони розкривають саму основу SFINAE-розміру-трюку.FooFindertrue_typefalse_type

Це TypeSinkструктура шаблону, яка пізніше використовується для миття інтегрального результатуsizeof оператора в інстанціювання шаблону для формування типу.

The matchФункція інший SFINAE вид шаблону , який залишається без загального аналога. Таким чином, він може бути створений лише в тому випадку, якщо тип його аргументу відповідає типу, для якого він був спеціалізований.

Обидві testфункції разом із декларацією enum нарешті утворюють центральний шаблон SFINAE. Існує загальний, який використовує еліпсис, який повертаєfalse_type і аналог з більш конкретними аргументами, щоб мати перевагу.

Щоб мати можливість інстанціювати testфункцію за допомогою аргументу шаблону T, matchфункція повинна бути ініційованою, оскільки для повернення потрібний її тип повернення TypeSink. Застереження полягає в тому &U::foo, що, обернувшись аргументом функції, не посилається зсередини спеціалізації аргументу шаблону, тому пошук спадкового члена все ще має місце.


1

Якщо ви використовуєте 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;
}

Хоча деталі реалізації однакові з попередньою відповіддю, користуватися бібліотекою простіше.


0

У мене була подібна потреба і я натрапив на це ТАК. Тут пропонується безліч цікавих / потужних рішень, хоча це просто триває лише для конкретної потреби: виявіть, чи має клас функцію члена з точною підписом. Тож я трохи прочитав / тестував і придумав свою версію, яка могла б зацікавити. Він виявляє:

  • статична функція члена
  • нестатична функція члена
  • нестатична функція члена 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 <typename 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

0

Грунтуючись на Джрок «s відповідь , я б уникнути використання вкладених класів і / або функції шаблону.

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

Пропозиції вітаються.

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