29

In https://stackoverflow.com/a/1967183/134841, a solution is provided for statically checking whether a member exists, possibly in a subclass of a type:

template <typename Type> 
class has_resize_method
{ 
   class yes { char m;}; 
   class no { yes m[2];}; 
   struct BaseMixin 
   { 
     void resize(int){} 
   }; 
   struct Base : public Type, public BaseMixin {}; 
   template <typename T, T t>  class Helper{}; 
   template <typename U> 
   static no deduce(U*, Helper<void (BaseMixin::*)(), &U::foo>* = 0); 
   static yes deduce(...); 
public: 
   static const bool result = sizeof(yes) == sizeof(deduce((Base*)(0))); 
};

However, it doesn't work on C++11 final classes, because it inherits from the class under test, which final prevents.

OTOH, this one:

template <typename C>
struct has_reserve_method {
private:
    struct No {};
    struct Yes { No no[2]; };
    template <typename T, typename I, void(T::*)(I) > struct sfinae {};
    template <typename T> static No  check( ... );
    template <typename T> static Yes check( sfinae<T,int,   &T::reserve> * );
    template <typename T> static Yes check( sfinae<T,size_t,&T::reserve> * );
public:
    static const bool value = sizeof( check<C>(0) ) == sizeof( Yes ) ;
};

fails to find the reserve(int/size_t) method in baseclasses.

Is there an implementation of this metafunction that both finds reserved() in baseclasses of T and still works if T is final?

Community
  • 1
  • 1
Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90

2 Answers2

51

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Doesn't `T` need to be a literal type in order to be a parameter to a `constexpr` function? Ok, on templates, the `constexpr` is dropped at instantiation time when it can't be `constexpr`, but you're using it in a template argument list, where it has to be. – Marc Mutz - mmutz Mar 02 '12 at 10:16
  • @Marc: No, you got nearly no restrictions on what can be a parameter. – Xeo Mar 02 '12 at 10:31
  • Ok, this gave me the right direction. After successive simplifications, I got it to work requiring minimal C++11 support (just `decltype`) by keeping the structure of the 2nd C++98 version, but replacing the `Yes` versions with a single `template static decltype(static_cast(0)->isNull(),Yes()) check(int);` one. – Marc Mutz - mmutz Mar 02 '12 at 11:06
  • 1
    @MarcMutz-mmutz: to avoid creating a null pointer, you can use `std::declval()` which will return a `T` (and you can use it for with `T const&` if you wish). – Matthieu M. Mar 02 '12 at 12:14
  • 2
    Passing a class type that is not trivial enough to `(...)` isn't a good idea (e.g. it's conditionally supported). I still prefer the traditional way that use unevaluated expressions. – Luc Danton Mar 03 '12 at 08:49
  • 2
    @Luc I think you can see it a different way: He does just function invocation substitution. Such a thing takes a function call expression and transforms it into another expression which doesn't have to be a function call anymore. In the end he has the expression `false`. – Johannes Schaub - litb Mar 03 '12 at 19:48
  • 6
    Note to future editors: I used *culls* (and not "calls") from [to cull](http://www.thefreedictionary.com/cull). – Matthieu M. Aug 14 '14 at 06:04
  • I'm getting errors for both the `struct` and the `std::enable_if` versions. In the former, gcc 4.8.2 says `error: 't' is not a constant expression` (and nothing more) at line: `Reserver::apply(t, n);`. For the latter, it says `use of parameter 't' outside function body` in both the `std::enable_if`s. Any clue? – Avio Aug 19 '14 at 15:25
  • That is strange, I tried coliru (to get clang) but could not get it to work (execution timed out). Normally with late declared return types, you can use the arguments names starting from `->`. – Matthieu M. Aug 19 '14 at 16:21
10

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.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114