Как определить, существует ли в классе конкретная переменная-член?

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

Вопрос

Для создания шаблонной функции алгоритма мне нужно знать, является ли x или X (и y или Y) в классе аргументом шаблона.Это может быть полезно при использовании моей функции для класса MFC CPoint или класса GDI + PointF или некоторых других.Все они используют в себе разные x.Мое решение могло бы быть сведено к следующему коду:


template<int> struct TT {typedef int type;};
template<class P> bool Check_x(P p, typename TT<sizeof(&P::x)>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<sizeof(&P::X)>::type b = 0) { return false; }

struct P1 {int x; };
struct P2 {float X; };
// it also could be struct P3 {unknown_type X; };

int main()
{
    P1 p1 = {1};
    P2 p2 = {1};

    Check_x(p1); // must return true
    Check_x(p2); // must return false

    return 0;
}

Но он не компилируется в Visual Studio, в то время как компилируется в GNU C ++.С Visual Studio я мог бы использовать следующий шаблон:


template<class P> bool Check_x(P p, typename TT<&P::x==&P::x>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<&P::X==&P::X>::type b = 0) { return false; }

Но он не компилируется на GNU C ++.Существует ли универсальное решение?

UPD:Структуры P1 и P2 приведены здесь только для примера.Могут быть любые классы с неизвестными членами.

P.S.Пожалуйста, не размещайте здесь решения C ++ 11, потому что они очевидны и не имеют отношения к данному вопросу.

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

Решение

Другой способ - это тот, который опирается на SFINAE для выражений слишком.Если поиск по имени приведет к неоднозначности, компилятор отклонит шаблон

template<typename T> struct HasX { 
    struct Fallback { int x; }; // introduce member name "x"
    struct Derived : T, Fallback { };

    template<typename C, C> struct ChT; 

    template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1]; 
    template<typename C> static char (&f(...))[2]; 

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

struct A { int x; };
struct B { int X; };

int main() { 
    std::cout << HasX<A>::value << std::endl; // 1
    std::cout << HasX<B>::value << std::endl; // 0
}

Это основано на блестящей идее кого-то в usenet.

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

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

Вот решение, более простое, чем Johannes Schaub - litb's один.Для этого требуется C ++ 11.

#include <type_traits>

template <typename T, typename = int>
struct HasX : std::false_type { };

template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

Обновить:Краткий пример и объяснение того, как это работает.

Для этих типов:

struct A { int x; };
struct B { int y; };

у нас есть HasX<A>::value == true и HasX<B>::value == false.Давайте посмотрим, почему.

Сначала напомним , что std::false_type и std::true_type иметь static constexpr bool имя участника value который установлен в false и true, соответственно.Следовательно, два шаблона HasX выше наследовать этот элемент.(Первый шаблон из std::false_type а второй из std::true_type.)

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

1) Отправная точка:

template <typename T, typename U>
struct HasX : std::false_type { };

В этом случае нет ничего удивительного: HasX происходит из std::false_type и следовательно HasX<bool, double>::value == false и HasX<bool, int>::value == false.

2) Невыполнение обязательств U:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

Учитывая , что U значение по умолчанию равно int, Has<bool> на самом деле означает HasX<bool, int> и таким образом, HasX<bool>::value == HasX<bool, int>::value == false.

3) Добавление специализации:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX<T, int> : std::true_type { };

В общем, благодаря основному шаблону, HasX<T, U> происходит из std::false_type.Однако существует специализация для U = int который вытекает из std::true_type.Следовательно, HasX<bool, double>::value == false но HasX<bool, int>::value == true.

Благодаря умолчанию для U, HasX<bool>::value == HasX<bool, int>::value == true.

4) decltype и причудливый способ сказать int:

Здесь небольшое отступление, но, пожалуйста, потерпите меня.

В принципе (это не совсем правильно), decltype(expression) выдает тип выражение.Например, 0 имеет тип int таким образом, decltype(0) означает int.Аналогично, 1.2 имеет тип double и таким образом, decltype(1.2) означает double.

Рассмотрим функцию с таким объявлением:

char func(foo, int);

где foo это какой-то тип класса.Если f является объектом типа foo, тогда decltype(func(f, 0)) означает char (тип , возвращаемый func(f, 0)).

Теперь, выражение (1.2, 0) использует (встроенный) оператор запятой, который вычисляет два подвыражения по порядку (то есть сначала 1.2 и тогда 0), отбрасывает первое значение и приводит ко второму.Следовательно,

int x = (1.2, 0);

эквивалентно

int x = 0;

Соединяя это вместе с decltype дает это decltype(1.2, 0) означает int.В этом нет ничего действительно особенного 1.2 или double вот.Например, true имеет тип bool и decltype(true, 0) означает int также.

Как насчет типа класса?Например, что делает decltype(f, 0) в смысле?Естественно ожидать, что это все еще означает int но это может быть и не так.Действительно, для оператора запятой может возникнуть перегрузка, аналогичная функции func выше это занимает foo и еще int и возвращает char.В данном случае, decltype(foo, 0) является char.

Как мы можем избежать использования перегрузки для оператора запятой?Ну, нет никакого способа перегрузить оператор запятой для void операнд, и мы можем привести что угодно к void.Следовательно, decltype((void) f, 0) означает int.В самом деле, (void) f забросы f От foo Для void который в принципе ничего не делает, кроме как говорит, что выражение должно рассматриваться как имеющее тип void.Затем используется встроенный оператор comma и ((void) f, 0) приводит к 0 который имеет тип int.Следовательно, decltype((void) f, 0) означает int.

Действительно ли этот актерский состав необходим?Ну, если нет перегрузки для оператора запятой, принимающего foo и int тогда в этом нет необходимости.Мы всегда можем проверить исходный код, чтобы увидеть, есть ли такой оператор или нет.Однако, если это появится в шаблоне и f имеет тип V который является параметром шаблона, то больше не ясно (или даже невозможно узнать), существует ли такая перегрузка для оператора запятой или нет.Чтобы быть универсальными, мы все равно разыгрываем.

Итог: decltype((void) f, 0) это причудливый способ сказать int.

5) СФИНАЕ:

Это целая наука ;-) ЛАДНО, я преувеличиваю, но это тоже не очень просто.Так что я сведу объяснение к самому минимуму.

SFINAE расшифровывается как Сбой замены - это не Ошибка.Это означает, что когда параметр шаблона заменяется типом, может появиться недопустимый код на C ++, но, в некоторых обстоятельствах, вместо прерывания компиляции компилятор просто игнорирует нарушающий код, как будто его там не было.Давайте посмотрим, как это применимо к нашему случаю:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

Здесь, снова, decltype((void) T::x, 0) это причудливый способ сказать int но с пользой для SFINAE.

Когда T заменяется типом, может появиться недопустимая конструкция.Например, bool::x недопустим C ++, поэтому замена T с bool в T::x выдает недопустимую конструкцию.Согласно принципу SFINAE, компилятор не отвергает код, он просто игнорирует (его части).Точнее, как мы уже виделиHasX<bool> означает на самом деле HasX<bool, int>.Специализация для U = int должен быть выбран, но при создании его экземпляра компилятор находит bool::x и полностью игнорирует специализацию шаблона, как если бы ее не существовало.

На данный момент код, по сути, такой же, как в приведенном выше случае (2), где существует только основной шаблон.Следовательно, HasX<bool, int>::value == false.

Тот же аргумент , который использовался для bool удерживается для B с тех пор как B::x является недопустимой конструкцией (B не имеет члена x).Однако, A::x все в порядке, и компилятор не видит проблемы в создании экземпляра специализации для U = int (или, точнее, для U = decltype((void) A::x, 0)).Следовательно, HasX<A>::value == true.

6) Безымянный U:

Что ж, взглянув на код в (5) еще раз, мы видим, что имя U не используется нигде , кроме как в его объявлении (typename U).Затем мы можем присвоить второму аргументу шаблона неназванное имя и получим код, показанный в верхней части этого поста.

Я был перенаправлен сюда с вопрос который был закрыт как дубликат этого.Я знаю, что это старая тема, но я просто хотел предложить альтернативу (более простую?) реализация, которая работает с C ++ 11.Предположим, мы хотим проверить, есть ли у определенного класса вызываемая переменная-член id:

#include <type_traits>

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

template<typename T>
struct has_id<T, decltype(std::declval<T>().id, void())> : std::true_type { };

Вот и все.И вот как это было бы использовано (живой пример):

#include <iostream>

using namespace std;

struct X { int id; };
struct Y { int foo; };

int main()
{
    cout << boolalpha;
    cout << has_id<X>::value << endl;
    cout << has_id<Y>::value << endl;
}

Все можно сделать еще проще с помощью пары макросов:

#define DEFINE_MEMBER_CHECKER(member) \
    template<typename T, typename V = bool> \
    struct has_ ## member : false_type { }; \
    template<typename T> \
    struct has_ ## member<T, \
        typename enable_if< \
            !is_same<decltype(declval<T>().member), void>::value, \
            bool \
            >::type \
        > : true_type { };

#define HAS_MEMBER(C, member) \
    has_ ## member<C>::value

Который мог бы быть использован таким образом:

using namespace std;

struct X { int id; };
struct Y { int foo; };

DEFINE_MEMBER_CHECKER(foo)

int main()
{
    cout << boolalpha;
    cout << HAS_MEMBER(X, foo) << endl;
    cout << HAS_MEMBER(Y, foo) << endl;
}

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

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

Проверить наличие участника 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.
*/

template <typename... Args> struct ambiguate : public Args... {};

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

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

СОЗДАНИЕ_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                                                            \
    ;                                                                       \
}

СОЗДАНИЕ_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 СОЗДАТЬ ЭЛЕМЕНТ_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)

Повышение.Концептуальные черты предоставляет между другими некоторые макросы для определения признаков типа, как, например BOOST_TT_EXT_DEFINE_HAS_MEMBER(name), который определяет признак типа формы:

has_member_##name<T>

Это дает значение true , если T имеет тип элемента с именем .Однако обратите внимание, что при этом не будут обнаружены члены ссылочного типа.

В вашем случае этого будет достаточно добавить в заголовочный файл

BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x)

и проверьте следующим образом

BOOST_STATIC_ASSERT(has_member_x<P1>::value);

Используемая техника такая же, как и та, которая была объяснена в некоторых предыдущих ответах.

К сожалению, эта библиотека больше не поддерживается.Теперь, когда C ++ 0x не будет включать concept, эта библиотека вместе с SFINAE является идеальной заменой для работы с большинством концепций.

Почему бы вам не использовать специализацию подобным образом:

struct P1 {int x; };
struct P2 {int X; };

template<class P> 
bool Check_x(P p) { return true; }

template<> 
bool Check_x<P2>(P2 p) { return false; }

Второй ответ (litb's) на этот вопрос показывает, как обнаружить участника:

Можно ли написать шаблон для проверки существования функции?

Почему бы вам просто не создать шаблонные специализации Check_x ?

template<> bool Check_x(P1 p) { return true; }
template<> bool Check_x(P2 p) { return false; }

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

Являются ли функции (x, X, y, Y) из абстрактного базового класса, или их можно было бы реорганизовать, чтобы они были такими?Если это так, вы можете использовать макрос SUPERSUBCLASS() из Modern C ++ Design вместе с идеями из ответа на этот вопрос:

Отправка на основе типов во время компиляции

Мы можем получить во время компиляции: 0 - not_member, 1 - is_object, 2 - is_function для каждого требуемого класса и члена - объекта или функции: http://ideone.com/Fjm9u5

#include <iostream>
#include <type_traits>

#define IS_MEMBER(T1, M)    \
struct {        \
    struct verystrangename1 { bool M; };    \
    template<typename T> struct verystrangename2 : verystrangename1, public T { }; \
    \
    enum return_t { not_member, is_object, is_function }; \
    template<typename T, typename = decltype(verystrangename2<T>::M)> constexpr return_t what_member() { return not_member;  }  \
    template<typename T> typename std::enable_if<std::is_member_object_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_object; }   \
    template<typename T> typename std::enable_if<std::is_member_function_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_function; }   \
    constexpr operator return_t() { return what_member<T1>(); } \
}

struct t {
    int aaa;
    float bbb;
    void func() {}
};

// Can't be in function
IS_MEMBER(t, aaa) is_aaa_member_of_t;
IS_MEMBER(t, ccc) is_ccc_member_of_t;
IS_MEMBER(t, func) is_func_member_of_t;

// known at compile time
enum { const_is_aaa_member_of_t = (int)is_aaa_member_of_t };
static constexpr int const_is_func_member_of_t = is_func_member_of_t;

int main() {        
    std::cout << std::boolalpha << "0 - not_member, 1 - is_object, 2 - is_function \n\n" <<
        "is aaa member of t = " << is_aaa_member_of_t << std::endl << 
        "is ccc member of t = " << is_ccc_member_of_t << std::endl << 
        "is func member of t = " << is_func_member_of_t << std::endl << 
        std::endl;

    return 0;
}

Результат:

0 - not_member, 1 - is_object, 2 - is_function 

is aaa member of t = 1
is ccc member of t = 0
is func member of t = 2

Для класса / структуры:

struct t {
    int aaa;
    float bbb;
    void func() {}
};
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top