Как определить, есть ли конкретная переменная-член в классе? - PullRequest
57 голосов
/ 17 июня 2009

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


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, потому что они очевидны и не имеют отношения к вопросу.

Ответы [ 10 ]

79 голосов
/ 14 апреля 2013

Вот решение проще, чем Йоханнес Шауб - лит х один . Требуется 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.

А как насчет типа класса? Для instace, что означает 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. Затем используется встроенная запятая оператора и ((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, но с пользой для СФИНА.

Когда 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) Unnaming U:

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

47 голосов
/ 17 июня 2009

Другой способ, который использует 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 произвольного типа. Единственная цель введения имени члена состоит в том, чтобы иметь возможную неоднозначность для поиска имени члена - тип члена не важен.

30 голосов
/ 25 января 2013

Меня перенаправили сюда из вопроса , который был закрыт как дубликат этого. Я знаю, что это старый поток, но я просто хотел предложить альтернативную (более простую?) Реализацию, которая работает с 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;
}
7 голосов
/ 13 июня 2011

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

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

Проверка члена x в данном классе. Может быть var, func, class, union или enum:

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

Проверка функции члена void x():

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

Проверка переменной элемента x:

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

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

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

Проверка членского союза x:

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

Проверка перечисления членов x:

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

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

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

OR

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

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

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

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

Макросы (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)
4 голосов
/ 23 апреля 2010

Boost.ConceptTraits предоставляет между другими некоторые макросы для определения признаков типа, например, 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 не включает концепцию, эта библиотека вместе с SFINAE является идеальной заменой для работы с большинством концепций.

2 голосов
/ 17 июня 2009

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

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

2 голосов
/ 17 июня 2009

Почему бы вам не использовать такую ​​специализацию, как эта:

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; }
1 голос
/ 17 июня 2009

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

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

1 голос
/ 17 июня 2009

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

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

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

0 голосов
/ 27 июля 2016

Мы можем получить во время компиляции: 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() {}
};
...