Как определить, есть ли конкретная переменная-член в классе?
Для создания шаблонной функции алгоритма мне нужно знать, есть ли 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 здесь только для примера. Могут быть любые классы с неизвестными участниками.
PS Пожалуйста, не публикуйте здесь решения C++11, потому что они очевидны и не имеют отношения к вопросу.
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, с произвольным типом. Единственная цель введения имени члена состоит в том, чтобы иметь возможную неоднозначность для поиска имени члена - тип члена не важен.
Вот решение, более простое, чем решение Johannes Schaub - 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
также.
Что насчет типа класса? Для 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) Отмена имени 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;
}
Мы можем использовать выражение Требуется выражение C++20 для решения этой проблемы. h/t для @lefticus, который недавно опубликовал этот метод в C++ Weekly - Ep 242 - Design By Introspection in 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;
}
Вы можете увидеть это вживую здесь.
ОБНОВЛЕНИЕ: я недавно сделал еще кое-что с кодом, который я разместил в моем первоначальном ответе, поэтому я обновляю его, чтобы учесть изменения / дополнения.
Вот некоторые фрагменты использования: * Внутренности для всего этого находятся ниже
Проверить для участника 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;
};
Макросы (Эль Диабло!):
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)
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 является идеальной заменой для работы с большинством концепций.
Почему бы вам не использовать специализацию, как это:
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; }
Второй ответ (лит) на это показывает, как обнаружить участника:
Можно ли написать шаблон для проверки существования функции?
Являются ли функции (x, X, y, Y) из абстрактного базового класса или они могут быть реорганизованы, чтобы быть таковыми? Если это так, вы можете использовать макрос SUPERSUBCLASS() из Modern C++ Design, а также идеи из ответа на этот вопрос:
Почему бы вам просто не создать шаблонную специализацию Check_x?
template<> bool Check_x(P1 p) { return true; }
template<> bool Check_x(P2 p) { return false; }
Черт возьми, когда я об этом думаю. Если у вас есть только два типа, зачем вам вообще нужны шаблоны для этого?
Мы можем получить во время компиляции: 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() {}
};