“Inverse SFINAE” to avoid ambiguous overload

前端 未结 2 859
野性不改
野性不改 2020-12-21 17:36

How can I prevent the first template below from instantiating if the second template instantiates? (i.e. if both static_cast(0) and T::zero()

相关标签:
2条回答
  • 2020-12-21 18:01

    Without enable_if, relying on built-in rules of integer conversion ranks (conversion of 0 -> int is better than 0 -> char, which makes the former a first choice candidate, and the latter a viable second choice candidate):

    template <typename T>
    auto zero_helper(char) -> decltype(static_cast<T>(0))
    {
        return static_cast<T>(0);
    }
    
    template <typename T>
    auto zero_helper(int) -> decltype(T::zero())
    {
        return T::zero();
    }
    
    template <typename T>
    auto zero() -> decltype(auto)
    {
        return zero_helper<T>(0);
    }
    

    DEMO

    With your own enable_if predicate (similar to the std::void_t technique):

    #include <type_traits>
    
    template <typename...>
    struct voider { using type = void; };
    
    template <typename... Ts>
    using void_t = typename voider<Ts...>::type;
    
    template <typename T, typename = void_t<>>
    struct has_zero : std::false_type {};
    
    template <typename T>
    struct has_zero<T, void_t<decltype(T::zero())>> : std::true_type {};
    
    template <typename T>
    auto zero()
        -> typename std::enable_if<has_zero<T>::value, decltype(T::zero())>::type
    {
        return T::zero();
    }
    
    template <typename T>
    auto zero()
        -> typename std::enable_if<!has_zero<T>::value, T>::type
    {
        return static_cast<T>(0);
    }
    

    DEMO 2

    0 讨论(0)
  • 2020-12-21 18:19

    If you need to extend it to multiple overloads with fine grained control of overload rank, a common technique is to use tag dispatching.

    template<int r>
    struct rank : rank<r - 1> {};
    
    template<>
    struct rank<0> {};
    
    template<typename T>
    auto zero_impl(rank<0>) -> decltype(static_cast<T>(0)) {
        return static_cast<T>(0);
    }
    
    template<typename T>
    auto zero_impl(rank<1>) ->decltype(T::zero()) {
        return T::zero();
    }
    
    template<typename T>
    auto zero() { return zero_impl<T>(rank<10>{}); }
    

    Derived to base conversions will prefer the closest base class. Which translates to calling the overload with the highest rank. Since that one will have the best implicit conversion sequence in the eyes of the compiler.

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