Checking a member exists, possibly in a base class, C++11 version

Solution 1:

Actually, things got much easier in C++11 thanks to the decltype and late return bindings machinery.

Now, it's just simpler to use methods to test this:

// Culled by SFINAE if reserve does not exist or is not accessible
template <typename T>
constexpr auto has_reserve_method(T& t) -> decltype(t.reserve(0), bool()) {
  return true;
}

// Used as fallback when SFINAE culls the template method
constexpr bool has_reserve_method(...) { return false; }

You can then use this in a class for example:

template <typename T, bool b>
struct Reserver {
  static void apply(T& t, size_t n) { t.reserve(n); }
};

template <typename T>
struct Reserver <T, false> {
  static void apply(T& t, size_t n) {}
};

And you use it so:

template <typename T>
bool reserve(T& t, size_t n) {
  Reserver<T, has_reserve_method(t)>::apply(t, n);
  return has_reserve_method(t);
}

Or you can choose a enable_if method:

template <typename T>
auto reserve(T& t, size_t n) -> typename std::enable_if<has_reserve_method(t), bool>::type {
  t.reserve(n);
  return true;
}

template <typename T>
auto reserve(T& t, size_t n) -> typename std::enable_if<not has_reserve_method(t), bool>::type {
  return false;
}

Note that this switching things is actually not so easy. In general, it's much easier when just SFINAE exist -- and you just want to enable_if one method and not provide any fallback:

template <typename T>
auto reserve(T& t, size_t n) -> decltype(t.reserve(n), void()) {
  t.reserve(n);
}

If substitution fails, this method is removed from the list of possible overloads.

Note: thanks to the semantics of , (the comma operator) you can chain multiple expressions in decltype and only the last actually decides the type. Handy to check multiple operations.

Solution 2:

A version that also relies on decltype but not on passing arbitrary types to (...) [ which is in fact a non-issue anyway, see Johannes' comment ]:

template<typename> struct Void { typedef void type; };

template<typename T, typename Sfinae = void>
struct has_reserve: std::false_type {};

template<typename T>
struct has_reserve<
    T
    , typename Void<
        decltype( std::declval<T&>().reserve(0) )
    >::type
>: std::true_type {};

I'd like to point out according to this trait a type such as std::vector<int>& does support reserve: here expressions are inspected, not types. The question that this trait answers is "Given an lvalue lval for such a type T, is the expressions lval.reserve(0); well formed". Not identical to the question "Does this type or any of its base types has a reserve member declared".

On the other hand, arguably that's a feature! Remember that the new C++11 trait are of the style is_default_constructible, not has_default_constructor. The distinction is subtle but has merits. (Finding a better fitting name in the style of is_*ible left as an exercise.)

In any case you can still use a trait such as std::is_class to possibly achieve what you want.