Search code examples
c++c++11finaltemplate-meta-programmingtype-traits

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


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?


Solution

  • 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.