3

I came across the Member Detector idiom in C++, which is a type-trait to tell if a class contains a member of a certain name. But the linked example does not work as I expected if the type is not a class: I wanted a false result for any non-class type. A possible solution is certainly something like this, using boost::is_class<T>:

template<typename T>
struct general_DetectX : boost::mpl::and_<
    boost::is_class<T>,
    DetectX<T> >::type
{ };

bool hasX = general_DetectX<int>::value; // hasX = false

But this question is about why the original DetectX<T> produces errors instead of doing the SFINAE thing. Here is an excerpt of the relevant parts of the linked code (local structs Fallback and Check<U,U> and typedefs ArrayOfOne, ArrayOfTwo and type removed for brevity):

template<typename T>
class DetectX
{
      struct Derived : T, Fallback { };

      template<typename U> 
      static ArrayOfOne & func(Check<int Fallback::*, &U::X> *);

      template<typename U> 
      static ArrayOfTwo & func(...);

  public:
      enum { value = sizeof(func<Derived>(0)) == 2 };
};

It can be seen that DetectX::Derived is used outside of any overload resolution, so the SFINAE rule for handling errors is never invoked. But this can be changed to where use of Derived does happen as part of the overload resolution:

template<typename T>
class DetectX
{
    template<typename U>
    struct Derived : U, Fallback { };

    template<typename U>
    static ArrayOfOne & func(Check<int Fallback::*, &Derived<U>::X> *);

    template<typename U>
    static ArrayOfTwo & func(...);

public:
    enum { value = sizeof(func<T>(0)) == 2 };
};

The Derived<T> template is only instantiated when trying to instantiate the first func() overload, so why do I still get errors even for the modified version? Here is an example:

$ g++ --version | head -n1
g++ (GCC) 4.8.2
$ g++ -c demo.cxx
demo.cxx: In instantiation of 'struct DetectX<int>::Derived<int>':
demo.cxx:16:53:   required by substitution of 'template<class U> static char (& DetectX<T>::func(DetectX<T>::Check<int DetectX<T>::Fallback::*, (& DetectX<T>::Derived<U>::X)>*))[1] [with U = U; T = int] [with U = int]'
demo.cxx:24:31:   required from 'class DetectX<int>'
demo.cxx:27:25:   required from here
demo.cxx:7:12: error: base type 'int' fails to be a struct or class type
     struct Derived : U, Fallback { };
            ^
Olaf Mandel
  • 787
  • 7
  • 20
  • It fails because the substitution happens in the list of base classes *where* any error results in hard-error as opposed to soft-error (SFINAE). In other words, SFINAE doesn't occur in base classes. – Nawaz Jun 13 '14 at 12:17
  • @Nawaz Thanks for the comment introducing me to hard vs soft errors. But I needed the link to http://stackoverflow.com/questions/15260685 provided by Jarod42 in his answer to get the context. – Olaf Mandel Jun 13 '14 at 13:11

1 Answers1

3

You may use: (https://ideone.com/LArNVO)

#include <cstdint>
#include <type_traits>

#define DEFINE_HAS_MEMBER(traitsName, memberName)                             \
    template <typename U>                                                     \
    class traitsName                                                          \
    {                                                                         \
    private:                                                                  \
        struct Fallback { int memberName; };                                  \
        struct Dummy {};                                                      \
        template<typename T, bool is_a_class = std::is_class<T>::value>       \
        struct identity_for_class_or_dummy { using type = Dummy; };           \
        template<typename T>                                                  \
        struct identity_for_class_or_dummy<T, true> { using type = T; };      \
                                                                              \
        template <typename Base>                                              \
        struct Derived : Base, Fallback {};                                   \
        template<typename T, T> struct helper;                                \
        template<typename T>                                                  \
        static std::uint8_t                                                   \
        check(helper<int (Fallback::*),                                       \
              &Derived<typename identity_for_class_or_dummy<T>::type>::memberName>*); \
        template<typename T> static std::uint16_t check(...);                 \
    public:                                                                   \
        static                                                                \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint16_t);  \
    }

DEFINE_HAS_MEMBER(has_foo, foo);

// Now test it:
class C{ public: int foo; };
class D : public C {};
class E {};

static_assert(has_foo<C>::value, "");
static_assert(has_foo<D>::value, "");
static_assert(!has_foo<E>::value, "");
static_assert(!has_foo<int>::value, "");

You may understand why it fails in your case in following question What is exactly the “immediate context” mentioned in the C++11 Standard for which SFINAE applies?

In short SFINAE applies to Derived<U>::X but not to Derived<U> (which is ill formed in your case).

Community
  • 1
  • 1
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thank you for the DEFINE_HAS_MEMBER example, but I can't use it in this application: 1. it uses C++11 but I use C++03 which I forgot to mention (minor: can be rewritten). 2. it needs a signature of the member, which the other solution does not need. But the link to http://stackoverflow.com/questions/15260685 was really helpful, basically answering my question: instantiation of `Derived` happens in the first step of instantiating `func`, where SFINAE does not apply. – Olaf Mandel Jun 13 '14 at 13:04
  • For *2.*, I have fixed my implementation. (as the signature for `T::foo` is not checked). – Jarod42 Jun 13 '14 at 13:28
  • thanks I used your technique for exactly this case too. – v.oddou Mar 06 '15 at 07:38