11
votes

Is it possible to check if a member variable, a member function, or a type definition is declared in a given class?

Various questions on StackOverflow talk about checking if a given class merely contains a member, essentially using std::is_detected. But all these solutions detect the member also in derived classes, which may not declare the member themselves.

For example, the following doesn't compile.

#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>);
}

Can any changes be made so that the two assertions hold? Or is reflection needed for that?

3
What result would be expected for struct derived : public base { using base::type; };? - user7860670
What is your usage ? You could be able to check for member Btw, but for type... - Jarod42
@VTT For this case I would expect to get "true". So I would expect it to behave as "base" in the example, since it declares its own member. But solutions that differ in that respect would also be interesting/right, I guess. - Toby Brull
@Jarod42 I want to tag certain classes that would be allowed for use as a template parameter to another template. I'm currently using a type-trait for this, but then users have to write a bit of boilerplate to opt in. Would be nicer if they could just say "using is_allowed_for_use = std::true_type;" or similar to opt in. So, checking for static members would be just as good for me. - Toby Brull
I would think some form of reflection would be needed for what you're describing, unless we can find some idiomatic way to properly hide type in every derived class (but even then it wouldn't be a "plug-and-play" solution) - AndyG

3 Answers

1
votes

I don't see a way for type or static member, but for regular member, you can have distinguish base::m from 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;

And the test:

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>);

Demo

0
votes

I'm inclined to say no. Proving that is of course hard, but I can explain why I think so.

C++ is a compiled language. The compiler has an internal representation of types, which you can't access directly. The only way to access this internal representation is through the facilities of the language. Actual implementations can vary in the way they represent types internally, and often do have additional information to produce better error messages. But this is not exposed.

So yes, most compilers can enumerate base types, and know exactly where each member of a class came from. That's essential for good error messages. But you can't enumerate base classes at compile time, using only C++ templates.

You might think that for data members you could try tricks with addresses and offsets. That won't work, again because you need to know the size of all base classes - and you can't enumerate those.

You might also consider tricks in which you create further-derived helper classes. That's possible, but they suffer from the same problem. You can only derive from the given type, not from its parent(s). It's thus possible to create a child class, but not a sibling. And that child inherits from parent and grandparent alike. Even if you wrote using derived::type in the helper class, that would find base::type.

0
votes

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.