11

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?

levzettelin
  • 2,600
  • 19
  • 32
  • 1
    What result would be expected for `struct derived : public base { using base::type; };`? – user7860670 Jan 25 '18 at 11:01
  • 1
    What is your usage ? You could be able to check for member Btw, but for type... – Jarod42 Jan 25 '18 at 11:02
  • @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. – levzettelin Jan 25 '18 at 11:09
  • @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. – levzettelin Jan 25 '18 at 11:13
  • 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 Jan 25 '18 at 12:44
  • Your use case sounds better served with a giant rtfm somewhere – Passer By Jan 25 '18 at 12:49
  • @TobiasBrüll: You solve that by `template using is_allowed_for_use = false_type` so opt-in becomes `template<> using is_allowed_for_use = true_type`. – MSalters Jan 25 '18 at 15:36
  • @MSalters Hmm, I was quite excited about this, as it would reduce my boilerplate code, but it actually doesn't see to work, see https://stackoverflow.com/questions/6622452/alias-template-specialisation – levzettelin Jan 25 '18 at 17:30
  • @TobiasBrüll: Sorry, overlooked that. You'll need a template class then, but that can just be an empty class inheriting from false_type (base case) / true_type (specialized). – MSalters Jan 25 '18 at 17:59

3 Answers3

1

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

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.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 2
    For members, you can know that: [Demo](http://coliru.stacked-crooked.com/a/01ad4de5522b5435) – Jarod42 Jan 25 '18 at 12:58
  • @Jarod42: It looks like that is the correct answer, and (summarized) your trick is that `&Derived::member` has some type `Base::*` when it's inherited. May I suggest you turn that into an answer? – MSalters Jan 25 '18 at 15:03
  • Adding a dummy member to the class seems not the clean way to solve OP problem. `type` or static member would do (but then I don't see a way to check that). – Jarod42 Jan 25 '18 at 15:22
  • I agree that @Jarod42 's answer is most constructive regarding the original question (as the question also asks about member variables), even though it doesn't really solve my problem. Therefore it should be selected as the solution. Other than that, I think this answer here is probably the most accurate, though its conclusion is a bit sad. – levzettelin Jan 25 '18 at 17:41
0

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.

Oliv
  • 17,610
  • 1
  • 29
  • 72