34

I want to write a type trait to check if some type has a member member. If member were public, there are any number of ways to do this (e.g. void_t), the most concise of which is probably Yakk's can_apply (which could eventually be called std::is_detected):

struct C {
    int member;
};

template <typename T>
using member_type = decltype(&T::member);

template <typename T>
using has_member = can_apply<member_type, T>;

static_assert(has_member<C>{}, "!"); // OK

But if the member were private, this trait fails, since the access on member is ill-formed (we are not friends) and there is no differentiation between ill-formed due to access reasons and ill-formed due to this-thing-doesn't-exist reasons:

class D {
    int member;
};

static_assert(has_member<D>{}, "!"); // error

Is there a way to write such a member checker across all access controls?

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 13
    Not trying to say this is a bad idea. I'm not qualified to do so. But I am genuinely curious, what would be the use of this? I.e., what's the use case of querying `private` characteristics of a type (which you couldn't access anyhow)? – DevSolar Sep 14 '15 at 13:59
  • See http://stackoverflow.com/questions/257288/possible-for-c-template-to-check-for-a-functions-existence#264088 – mvw Sep 14 '15 at 14:02
  • 7
    If `member` is private, you should be able to safely rename that member without breaking external code that merely happens to be using your class. Being able to create a `has_member` template the way you're using would make it *very* easy to create code that at the very least does not act the way another developer might reasonably expect code to act. So I share DevSolar's question: what use do you have for this? –  Sep 14 '15 at 14:04
  • 3
    @DevSolar I was genuinely curious to see if such a thing was possible. Name lookup itself doesn't consider access, so it seems like it should be possible to wrap in a type trait. – Barry Sep 14 '15 at 14:54

2 Answers2

30

There is indeed a way for non-final non-union class types:

namespace detail {
    struct P {typedef int member;};
    template <typename U>
    struct test_for_member : U, P
    {
        template <typename T=test_for_member, typename = typename T::member>
        static std::false_type test(int);
        static std::true_type test(float);
    };
}
template <typename T>
using test_for_member =
  std::integral_constant<bool, decltype(detail::test_for_member<T>::test(0)){}>;

Demo. The trick is to check whether lookup into different base classes will yield an ambiguity. [class.member.lookup]/2:

Member name lookup determines the meaning of a name (id-expression) in a class scope (3.3.7). Name lookup can result in an ambiguity, in which case the program is ill-formed. […] Name lookup takes place before access control (3.4, Clause 11).

Note that GCCs lookup is broken insofar as it ignores non-type names for lookup in typename-specifiers.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 1
    I always found it entirely counterintuitive that lookup was independent of protection. Strange that C++ allows for identifiers which, in certain contexts, are a *complication* without being at all *useful*. – Sneftel Sep 14 '15 at 22:10
9

You can create another class MemberBase that does have that member, and then subclass the two classes (the class to check T and BaseMember) and try to access the member of the subclass. If T also has a member member, then you will get an ambiguity problem.

Code:

#include <type_traits>

// Yakk's can_apply

template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

template<class...>struct types{using type=types;};
namespace details {
  template<template<class...>class Z, class types, class=void>
  struct can_apply : std::false_type {};
  template<template<class...>class Z, class...Ts>
  struct can_apply< Z, types<Ts...>, void_t< Z<Ts...> > >:
    std::true_type
  {};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z,types<Ts...>>;

// Main code

class MemberBase {
    public:
        int member;
};

template<class ToCheck>
class MemberCheck: public ToCheck, public MemberBase {
};

template <typename T>
using member_type = decltype(&T::member);

template <typename T>
using hasnot_member = can_apply<member_type, MemberCheck<T>>;

template <typename T> 
using static_not = std::integral_constant<bool, !T::value>;

template <typename T>
using has_member = static_not<hasnot_member<T>>;

// Tests

class A {
    int member;
};

class Ap {
    public:
    int member;
};

class B {
    float member;
};

class C {
    int member();
};

class D {
};

static_assert(has_member<A>{}, "!"); // ok
static_assert(has_member<Ap>{}, "!"); // ok
static_assert(has_member<B>{}, "!"); // ok
static_assert(has_member<C>{}, "!"); // ok
static_assert(has_member<D>{}, "!"); // fail

However, this definitely smells like a dirty hack to me.

Petr
  • 9,812
  • 1
  • 28
  • 52