Проверьте, объявлен ли член в классе
Можно ли проверить, является ли переменная-член, функция-член или определение типа объявлена в заданном классе?
Различные вопросы в StackOverflow говорят о проверке того, содержит ли данный класс только элемент, по существу используя std:: is_detected. Но все эти решения обнаруживают член также в производных классах, которые могут не объявлять сам член.
Например, следующее не компилируется.
#include <experimental/type_traits>
struct base
{
using type = std::true_type;
};
struct derived : public base { };
template<typename T>
using has_type_t = typename T::type;
template<typename T>
constexpr inline bool has_type_v =
std::experimental::is_detected<has_type_t, T>::value;
int main ()
{
static_assert (has_type_v<base>);
static_assert (!has_type_v<derived>);
}
Могут ли быть сделаны какие-либо изменения, чтобы выполнялись два утверждения? Или это необходимо для размышления?
Ответы
Ответ 1
Я не вижу способ для типа или статического члена, но для обычного члена вы можете отличить base::m
от derived::m
:
template<typename T>
using has_m_t = decltype(&T::m);
template<typename T>
constexpr inline bool has_m_v =
std::experimental::is_detected_exact<int (T::*), has_m_t, T>::value;
И тест:
struct base { int m; };
struct derived : public base {};
struct without {};
static_assert( has_m_v<base>);
static_assert(!has_m_v<derived>);
static_assert(!has_m_v<without>);
Демо
Ответ 2
Я склонен сказать "нет". Доказательство, конечно, тяжелое, но я могу объяснить, почему я так думаю.
С++ - это скомпилированный язык. Компилятор имеет внутреннее представление типов, к которым вы не можете получить доступ напрямую. Единственный способ получить доступ к этому внутреннему представлению - это средства языка. Фактические реализации могут различаться в том, как они представляют типы внутри, и часто имеют дополнительную информацию для получения более эффективных сообщений об ошибках. Но это не раскрывается.
Итак, большинство компиляторов могут перечислять базовые типы и точно знать, откуда появился каждый член класса. Это важно для хороших сообщений об ошибках. Но вы не можете перечислять базовые классы во время компиляции, используя только шаблоны С++.
Вы можете подумать, что для членов данных вы можете попробовать трюки с адресами и смещениями. Это не сработает, потому что вам нужно знать размер всех базовых классов, и вы не можете их перечислять.
Вы также можете рассмотреть трюки, в которых вы создаете дополнительные вспомогательные классы. Это возможно, но они страдают от одной и той же проблемы. Вы можете получить только данный тип, а не его родителя (ов). Таким образом, можно создать дочерний класс, но не родного брата. И этот ребенок наследует от родителей и дедушек и бабушек. Даже если вы написали using derived::type
в классе-помощнике, это нашло бы base::type
.
Ответ 3
Вы можете с ограничением, ваш компилятор должен иметь встроенный, который предоставляет список базовых классов (GCC предоставляет его).
Если у вас есть доступ к такому en intrinsic, единственная трудность состоит в том, чтобы проверить, что доступ к члену через производный не является действительным для доступа к члену базы.
Чтобы проверить это, идея заключается в использовании "двусмысленного" доступа, который происходит при доступе к члену, объявленному в нескольких базовых классах производного класса:
struct base
{
using type = std::true_type;
};
struct Derived1 : base{
};
struct Derived2 : base{
using type = std::true_type;
};
struct Test1: Derived1,base{};
struct Test2: Derived2,base{};
void g(){
Test1::type a;
Test2::type b;//Do not compile: 'type' is ambiguous
}
Итак, вы можете обобщить этот трюк следующим образом:
template<class T,class Base>
struct MultiDer: T,Base{};
template<class T,class Base,class=void>
struct has_ambiguous_type
:std::true_type{};
template<class T,class Base>
struct has_ambiguous_type
<T,Base,std::void_t<typename MultiDer<T,Base>::type>>
:std::false_type{};
template<class T,class=void>
struct has_type
:std::false_type{};
template<class T>
struct has_type
<T,std::void_t<typename T::type>>
:std::true_type{};
template<class T,class...Bases>
constexpr inline auto has_type_declared_imp =
has_type<T>::value
//if any ambiguous access happens then T has 'type'
&& ( (has_ambiguous_type<T,Bases>::value || ...)
//or no ambiguity happened because none of the base has 'type'
|| (!has_type<Bases>::value && ...));
template<class T>
constexpr inline auto has_type_declared =
has_type_declared_imp<T,__direct_bases(T)...>;//GCC instrinsic
static_assert(has_type_declared<Derived2>);
static_assert(!has_type_declared<Derived1>);
Единственная проблема - переносимость: ваш компилятор должен предоставить механизм для доступа к списку прямых оснований типа. Здесь я использовал встроенный GCC __direct_bases
.