Calling a free function instead of a method if it doesn't exist

前端 未结 4 809
滥情空心
滥情空心 2021-02-01 07:33

Suppose you have a family of type-unrelated classes implementing a common concept by means of a given method returning a value:

class A { public: int val() const         


        
相关标签:
4条回答
  • 2021-02-01 07:43

    You can use return-type SFINAE:

    template<typename T>
    auto val_of(const T& t) -> decltype(std::declval<T>().val())
    {
        return t.val();
    }
    
    int val_of(...)
    {
        return 0;
    }
    
    0 讨论(0)
  • 2021-02-01 07:46

    Just a somewhat longer comment... Your question has been answered. But I recently had a similar problem. Say you want to write a method to print strings to cout: Use member function write(std::cout), if not available use free function to_string(), if not available fallback to operator<<. You can use expression SFINAE as in the answer and a little class hierarchy to disambiguate the overloads:

    struct S3 {};
    struct S2 : S3 {};
    struct S1 : S2 {};
    
    template <class T>
    auto print(S1, T const& t) -> decltype(t.write(std::cout)) {
        t.write(std::cout);
    }
    
    template <class T>
    auto print(S2, T const& t) -> decltype(std::cout << to_string(t)) {
        std::cout << to_string(t);
    }
    
    template <class T>
    void print(S3, T const& t) {
        std::cout << t;
    }
    
    template <class T>
    void print(T const& t) {
        print(S1(), t);
    }
    
    0 讨论(0)
  • 2021-02-01 07:57

    The currently highest voted answer invokes undefined behavior in some cases, so I will give an alternative answer.

    We start with some boilerplate machinery:

    template<typename T> struct type_sink { typedef void type; }
    template<typename T> using TypeSink = typename type_sink<T>::type;
    

    Then a has_val traits class:

    template<typename T, typename=void>
    struct has_val : std::false_type;
    template<typename T>
    struct has_val<T, TypeSink< decltype(std::declval<T>().val()) > > : std::true_type;
    

    We can then use tag dispatching to solve your problem:

    template<typename T>
    int val_of_internal( T const& t, std::true_type /* has_val */ ) {
      return t.val();
    }
    template<typename T>
    int val_of_internal( T const& t, std::false_type /* has_val */ ) {
      return 0;
    }
    template<typename T>
    int val_of( T const& t ) {
      return val_of_internal( t, has_val<T const>() );
    }
    

    If you find writing has_val tiresome and prefer macros, here is a set of macros that write the has_val for you:

    #define EXPRESSION_IS_VALID_FOR_TYPE_T_TRAIT( TRAIT_NAME, ... ) \
    template<typename T, typename=void> \
    struct TRAIT_NAME : std::false_type {}; \
    template<typename T> \
    struct TRAIT_NAME< T, TypeSink< decltype( __VA_ARGS__ ) > >: std::true_type {}
    
    #define HAS_NULLARY_METHOD_TRAIT(TRAIT_NAME, METHOD_NAME) \
    EXPRESSION_IS_VALID_FOR_TYPE_T_TRAIT( TRAIT_NAME, std::declval<T>().METHOD_NAME() )
    

    Now we can write this:

    HAS_NULLARY_METHOD_TRAIT( has_val, val );
    

    but I do not know if it is worth it.

    0 讨论(0)
  • 2021-02-01 08:01

    Use this type trait class to determine whether a type has got val member function.

    template<typename U>
    struct has_val {
        typedef char yes;
        typedef struct { char c[2]; } no;
    
        template<typename X>
        static constexpr auto test(int) -> decltype( std::declval<X>().val(), yes() );
    
        template<typename X>
        static constexpr no test(...);
    
        static constexpr bool value = sizeof(test<U>(0)) == sizeof(yes);
    };
    

    You can use it as a condition to enable_if.

    0 讨论(0)
提交回复
热议问题