You can but with a limitation, your compiler must have an intrinsic that provides the list of base classes (GCC provides it).
If you can have access to such en intrinsic, the only difficulty is to check that member access through the derived is not actualy an access to a member of the base.
To check this, the idea is to use the 'ambiguous' access that happens when accessing a member declared in multiple bases of a derived class:
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
}
So you can generalize this trick this way:
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>);
The only problem is portability: your compiler must provides a mechanism to get access to the list of direct bases of a type. Here I have used the GCC's intrinsic __direct_bases
.