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

76
Kirill V. Lyadvinsky 17 Июн 2009 в 10:58

10 ответов

Лучший ответ

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

50
Antonio 25 Мар 2015 в 09:28

Вот решение проще, чем Йоханнес Шауб - один litb . Требуется 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) для instace? Естественно ожидать, что это по-прежнему означает 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, но с преимуществом 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). Затем мы можем отменить имя второго аргумента шаблона и получить код, показанный в верхней части этого сообщения.

111
Community 23 Май 2017 в 12:26

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

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

Вот несколько примеров использования:

Проверить член 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;
};

Макросы (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)
6
Brett Rossier 28 Мар 2012 в 17:51

Boost.ConceptTraits предоставляет между другими макросами для определения свойств типов. , как, например, BOOST_TT_EXT_DEFINE_HAS_MEMBER(name), который определяет черту типа в форме:

has_member_##name<T>

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

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

BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x)

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

BOOST_STATIC_ASSERT(has_member_x<P1>::value);

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

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

4
Vicente Botet Escriba 22 Апр 2010 в 20:09

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

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; }
2
Naveen 17 Июн 2009 в 07:20

Мы можем использовать С ++ 20 требует выражения для решения этой проблемы. . h / t @lefticus, который недавно разместил этот метод на C ++ Weekly - Ep 242 - Дизайн через самоанализ в C ++ 20 (концепции + if constexpr:

#include <iostream>

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

bool has_x(const auto &obj) {
    if constexpr (requires {obj.x;}) {
      return true;
    } else
      return false;
}

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

    std::cout << std::boolalpha << has_x(p1) << "\n"; 
    std::cout << has_x(p2) << "\n"; 

    return 0;
}

Вы можете увидеть его здесь.

2
Shafik Yaghmour 19 Окт 2020 в 18:50

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

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

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

1
ralphtheninja 17 Июн 2009 в 07:42

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

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

1
Community 23 Май 2017 в 12:10

Мы можем получить во время компиляции: 0 - not_member, 1 - is_object, 2 - is_function для каждого требуемого класса и члена - объекта или функции: http: // ideone. ru / 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() {}
};
0
Alex 27 Июл 2016 в 12:56