Reputation: 827
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 { };
^
Upvotes: 3
Views: 365
Reputation: 217850
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).
Upvotes: 3