Проверьте, объявлен ли член в классе

Можно ли проверить, является ли переменная-член, функция-член или определение типа объявлена ​​ в заданном классе?

Различные вопросы в 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.