Check if a class has a member function of a given signature

后端 未结 17 1482
無奈伤痛
無奈伤痛 2020-11-22 03:00

I\'m asking for a template trick to detect if a class has a specific member function of a given signature.

The problem is similar to the one cited here http://www.go

相关标签:
17条回答
  • 2020-11-22 03:19

    With c++ 20 this becomes much simpler. Say we want to test if a class T has a member function void T::resize(typename T::size_type). For example, std::vector<U> has such a member function. Then,

    template<typename T>
    concept has_resize_member_func = requires {
        typename T::size_type;
        { std::declval<T>().resize(std::declval<typename T::size_type>()) } -> std::same_as<void>;
    };
    

    and the usage is

    static_assert(has_resize_member_func<std::string>, "");
    static_assert(has_resize_member_func<int> == false, "");
    
    0 讨论(0)
  • 2020-11-22 03:26

    You appear to want the detector idiom. The above answers are variations on this that work with C++11 or C++14.

    The std::experimental library has features which do essentially this. Reworking an example from above, it might be:

    #include <experimental/type_traits>
    
    // serialized_method_t is a detector type for T.serialize(int) const
    template<typename T>
    using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));
    
    // has_serialize_t is std::true_type when T.serialize(int) exists,
    // and false otherwise.
    template<typename T>
    using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;
    
    

    If you can't use std::experimental, a rudimentary version can be made like this:

    template <typename... Ts>
    using void_t = void;
    template <template <class...> class Trait, class AlwaysVoid, class... Args>
    struct detector : std::false_type {};
    template <template <class...> class Trait, class... Args>
    struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};
    
    // serialized_method_t is a detector type for T.serialize(int) const
    template<typename T>
    using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));
    
    // has_serialize_t is std::true_type when T.serialize(int) exists,
    // and false otherwise.
    template <typename T>
    using has_serialize_t = typename detector<serialized_method_t, void, T>::type;
    

    Since has_serialize_t is really either std::true_type or std::false_type, it can be used via any of the common SFINAE idioms:

    template<class T>
    std::enable_if_t<has_serialize_t<T>::value, std::string>
    SerializeToString(const T& t) {
    }
    

    Or by using dispatch with overload resolution:

    template<class T>
    std::string SerializeImpl(std::true_type, const T& t) {
      // call serialize here.
    }
    
    template<class T>
    std::string SerializeImpl(std::false_type, const T& t) {
      // do something else here.
    }
    
    template<class T>
    std::string Serialize(const T& t) {
      return SerializeImpl(has_serialize_t<T>{}, t);
    }
    
    
    0 讨论(0)
  • 2020-11-22 03:27

    Here is a simpler take on Mike Kinghan's answer. This will detect inherited methods. It will also check for the exact signature (unlike jrok's approach which allows argument conversions).

    template <class C>
    class HasGreetMethod
    {
        template <class T>
        static std::true_type testSignature(void (T::*)(const char*) const);
    
        template <class T>
        static decltype(testSignature(&T::greet)) test(std::nullptr_t);
    
        template <class T>
        static std::false_type test(...);
    
    public:
        using type = decltype(test<C>(nullptr));
        static const bool value = type::value;
    };
    
    struct A { void greet(const char* name) const; };
    struct Derived : A { };
    static_assert(HasGreetMethod<Derived>::value, "");
    

    Runnable example

    0 讨论(0)
  • 2020-11-22 03:28

    To be non-intrusive, you can also put serialize in the namespace of the class being serialised, or of the archive class, thanks to Koenig lookup. See Namespaces for Free Function Overrides for more details. :-)

    Opening up any given namespace to implement a free function is Simply Wrong. (e.g., you're not supposed to open up namespace std to implement swap for your own types, but should use Koenig lookup instead.)

    0 讨论(0)
  • 2020-11-22 03:30

    Okay. Second try. It's okay if you don't like this one either, I'm looking for more ideas.

    Herb Sutter's article talks about traits. So you can have a traits class whose default instantiation has the fallback behaviour, and for each class where your member function exists, then the traits class is specialised to invoke the member function. I believe Herb's article mentions a technique to do this so that it doesn't involve lots of copying and pasting.

    Like I said, though, perhaps you don't want the extra work involved with "tagging" classes that do implement that member. In which case, I'm looking at a third solution....

    0 讨论(0)
  • 2020-11-22 03:31

    This should be sufficient, if you know the name of the member function you are expecting. (In this case, the function bla fails to instantiate if there is no member function (writing one that works anyway is tough because there is a lack of function partial specialization. You may need to use class templates) Also, the enable struct (which is similar to enable_if) could also be templated on the type of function you want it to have as a member.

    template <typename T, int (T::*) ()> struct enable { typedef T type; };
    template <typename T> typename enable<T, &T::i>::type bla (T&);
    struct A { void i(); };
    struct B { int i(); };
    int main()
    {
      A a;
      B b;
      bla(b);
      bla(a);
    }
    
    0 讨论(0)
提交回复
热议问题