Как определить, существует ли конкретная переменная-член в классе?
Для создания функции шаблона алгоритма мне нужно знать, являются ли 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 С++. С 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 С++. Существует ли универсальное решение?
UPD: Структуры P1 и P2 здесь только, например. Могут быть любые классы с неизвестными членами.
P.S. Пожалуйста, не отправляйте решения С++ 11 здесь, потому что они очевидны и не имеют отношения к вопросу.
Ответы
Ответ 1
Другой способ - это тот, который опирается на 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, с произвольным типом. Единственная цель введения имени члена - иметь возможную двусмысленность для поиска имени члена - тип элемента не важен.
Ответ 2
Вот решение проще, чем Johannes Schaub - litb . Для этого требуется С++ 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
и возвращает a 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:
Это целая наука;-) ОК, я увлечен, но это тоже не очень просто. Поэтому я объясню это с минимальным минимумом.
SFINAE означает, что сбой замещения не является ошибкой. Это означает, что когда параметр шаблона заменяется типом, может появиться нелегальный код С++, но в некоторых случаях вместо прерывания компиляции компилятор просто игнорирует код нарушения, как будто его там не было. Посмотрим, как это применимо к нашему делу:
// 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
недопустим С++, поэтому замена 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
). Затем мы можем игнорировать второй аргумент шаблона, и мы получаем код, показанный в верхней части этого сообщения.
Ответ 3
Меня перенаправили сюда из question, который был закрыт как дубликат этого. Я знаю, что это старый поток, но я просто хотел предложить альтернативную (более простую?) Реализацию, которая работает с С++ 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;
}
Ответ 4
ОБНОВЛЕНИЕ: Недавно я сделал еще кое-что с кодом, который я опубликовал в своем первоначальном ответе, поэтому я обновляю это, чтобы учитывать изменения/дополнения.
Вот некоторые фрагменты использования:
* Кишки для всего этого дальше вниз
Проверить член 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;
Проверить членство union x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
Проверить членство enum 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)
Ответ 5
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);
Используемая техника такая же, как и поясняемая в некоторых предыдущих ответах.
К сожалению, эта библиотека больше не поддерживается. Теперь, когда С++ 0x не будет включать концепцию, эта библиотека вместе с SFINAE является идеальной заменой для работы с большинством концепций.
Ответ 6
Почему бы вам не воспользоваться такой специализацией:
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; }
Ответ 7
Второй ответ (litb) на это показывает, как определить участника:
Можно ли написать шаблон для проверки существования функции?
Ответ 8
Почему бы вам просто не создать специализированные шаблоны Check_x?
template<> bool Check_x(P1 p) { return true; }
template<> bool Check_x(P2 p) { return false; }
Черт, когда я думаю об этом. Если у вас есть только два типа, зачем вам нужны шаблоны для этого?
Ответ 9
Являются ли функции (x, X, y, Y) абстрактным базовым классом или могут быть реорганизованы так? Если это так, вы можете использовать макрос SUPERSUBCLASS() из Modern С++ Design, а также идеи ответа на этот вопрос:
Отправка на основе типа времени компиляции
Ответ 10
Мы можем получить во время компиляции: 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() {}
};