2

I am purposely using the very same title as this question because I feel that the answer that was accepted does not account for a problem that I am stuck into.

I am looking for a way to detect if some class has some member variable. It is fundamental to note that I am looking for a variable, not a member function or anything else.

Here is the example provided in the question I linked:

template<typename T> struct HasX { 
    struct Fallback { int x; }; // introduce member name "x"
    struct Derived : T, Fallback { };

    template<typename C, C> struct ChT; 

    template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1]; 
    template<typename C> static char (&f(...))[2]; 

    static bool const value = sizeof(f<Derived>(0)) == 2;
}; 

struct A { int x; };
struct B { int X; };

int main() { 
    std::cout << HasX<A>::value << std::endl; // 1
    std::cout << HasX<B>::value << std::endl; // 0
}

But we will get the very same output if we do something like

template<typename T> struct HasX { 
    struct Fallback { int x; }; // introduce member name "x"
    struct Derived : T, Fallback { };

    template<typename C, C> struct ChT; 

    template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1]; 
    template<typename C> static char (&f(...))[2]; 

    static bool const value = sizeof(f<Derived>(0)) == 2;
}; 

struct A { 
  void x()
  {
  }
};
struct B { int X; };

int main() { 
    std::cout << HasX<A>::value << std::endl; // 1
    std::cout << HasX<B>::value << std::endl; // 0
}

(Please note that in the second example the int x in A was substituted with a member function void x()).

I have no real idea on how to work around this problem. I partially fixed this by doing something like

template <bool, typename> class my_helper_class;

template <typename ctype> class my_helper_class <true, ctype>
{
  static bool const value = std :: is_member_object_pointer <decltype(&ctype :: x)> :: value;
};

template <typename ctype> class my_helper_class <false, ctype>
{
  static bool const value = false;
};

template <typename T> struct HasX
{
// ...

static bool const value = my_helper_class <sizeof(f <Derived>(0)) == 2, T> :: value;
};

Which actually selects if I am using an object. However, the above doesn't work if there are more overloaded functions with the same name x in my class.

For example if I do

struct A
{
   void x()
   {
   }

   void x(int)
   {
   }
};

Then the pointer is not resolved successfully and the a call to HasX <A> doesn't compile.

What am I supposed to do? Is there any workaround or simpler way to get this done?

Community
  • 1
  • 1
Matteo Monti
  • 8,362
  • 19
  • 68
  • 114

1 Answers1

2

The problem is that HasX only checks if the name x exists. The ... gets selected if &C::x is ambiguous (which happens if it matches both in Fallback and T). The ChT<> overload gets selected only if &C::x is exactly Fallback::x. At no point are we actually checking the type of T::x - so we never actually check if x is a variable or function or whatever.

The solution is: use C++11 and just check that &T::x is a member object pointer:

template <class T, class = void>
struct HasX
: std::false_type
{ };

template <class T>
struct HasX<T,
    std::enable_if_t<
        std::is_member_object_pointer<decltype(&T::x)>::value>
    >
: std::true_type { };

If &T::x doesn't exist, substitution failure and we fallback to the primary template and get false_type. If &T::x exists but is an overloaded name, substitution failure. If &T::x exists but is a non-overloaded function, substitution failure on enable_if_t<false>. SFINAE for the win.

That works for all of these types:

struct A { 
  void x()
  {
  }

  void x(int)
  {
  }
};

struct B { int X; };
struct C { int x; };
struct D { char x; };

int main() { 
    static_assert(!HasX<A>::value, "!");
    static_assert(!HasX<B>::value, "!");
    static_assert(HasX<C>::value, "!");
    static_assert(HasX<D>::value, "!");
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • @MatteoMonti Well, yeah, `D::x` is a `char` and you want it to be an `int`? Also, too many spaces! Just `std::cout << HasX::value` please... – Barry Apr 13 '16 at 16:51
  • Okay, maybe I didn't explain myself well enough. What I would need is to catch any member variable with that name! It doesn't have to be an integer, but it has to be a variable! The test should yield true on `C` and `D`, false on the rest of the structures.. – Matteo Monti Apr 13 '16 at 16:53
  • The fact that I don't know what should be the type of `X` makes it difficult for me to devise a way to use `ChT` so that I can detect if `&T::X` is resolved. I should know what the type of `&T::X` is, which I don't. And if I use `decltype` I get into an error if `&T::X` is not resolved. So what can we do? – Matteo Monti Apr 13 '16 at 17:04
  • @MatteoMonti Actually, why am I writing a C++03 solution. Here's the modern way of doing it. – Barry Apr 13 '16 at 17:11
  • Okay. That is so much more compact. Thank you so much. – Matteo Monti Apr 13 '16 at 17:14