Detect operator support with decltype/SFINAE

前端 未结 5 1805
夕颜
夕颜 2020-12-05 01:12

A (somewhat) outdated article explores ways to use decltype along with SFINAE to detect if a type supports certain operators, such as == or &

相关标签:
5条回答
  • 2020-12-05 01:51

    This is C++0x, we don't need sizeof-based tricks any more... ;-]

    #include <type_traits>
    #include <utility>
    
    namespace supports
    {
        namespace details
        {
            struct return_t { };
        }
    
        template<typename T>
        details::return_t operator <(T const&, T const&);
    
        template<typename T>
        struct less_than : std::integral_constant<
            bool,
            !std::is_same<
                decltype(std::declval<T const&>() < std::declval<T const&>()),
                details::return_t
            >::value
        > { };
    }
    

    (This is based on iammilind's answer, but doesn't require that T's operator< return-type be a different size than long long and doesn't require that T be default-constructable.)

    0 讨论(0)
  • 2020-12-05 01:55

    In C++11 the shortest most general solution I found was this one:

    #include <type_traits>
    
    template<class T, class = decltype(std::declval<T>() < std::declval<T>() )> 
    std::true_type  supports_less_than_test(const T&);
    std::false_type supports_less_than_test(...);
    
    template<class T> using supports_less_than = decltype(supports_less_than_test(std::declval<T>()));
    
    #include<iostream>
    struct random_type{};
    int main(){
        std::cout << supports_less_than<double>::value << std::endl; // prints '1'
        std::cout << supports_less_than<int>::value << std::endl; // prints '1'
        std::cout << supports_less_than<random_type>::value << std::endl; // prints '0'
    }
    

    Works with g++ 4.8.1 and clang++ 3.3


    A more general solution for arbitrary operators (UPDATE 2014)

    There is a more general solution that exploits the fact that all built-in operators are also accessible (and posibly specialized) through STD operator wrappers, such as std::less (binary) or std::negate (unary).

    template<class F, class... T, typename = decltype(std::declval<F>()(std::declval<T>()...))> 
    std::true_type  supports_test(const F&, const T&...);
    std::false_type supports_test(...);
    
    template<class> struct supports;
    template<class F, class... T> struct supports<F(T...)> 
    : decltype(supports_test(std::declval<F>(), std::declval<T>()...)){};
    

    This can be used in a quite general way, especially in C++14, where type deduction is delayed to the operator wrapper call ("transparent operators").

    For binary operators it can be used as:

    #include<iostream>
    struct random_type{};
    int main(){
        std::cout << supports<std::less<>(double, double)>::value << std::endl; // '1'
        std::cout << supports<std::less<>(int, int)>::value << std::endl; // '1'
        std::cout << supports<std::less<>(random_type, random_type)>::value << std::endl; // '0'
    }
    

    For unary operators:

    #include<iostream>
    struct random_type{};
    int main(){
        std::cout << supports<std::negate<>(double)>::value << std::endl; // '1'
        std::cout << supports<std::negate<>(int)>::value << std::endl; // '1'
        std::cout << supports<std::negate<>(random_type)>::value << std::endl; // '0'
    }
    

    (With the C++11 standard library is a bit more complicated because there is no failure on instatiating decltype(std::less<random_type>()(...)) even if there is no operation defined for random_type, one can implement manually transparent operators in C++11, that are standard in C++14)

    The syntax is quite smooth. I hope something like this is adopted in the standard.


    Two extensions:

    1) It works to detect raw-function applications:

    struct random_type{};
    random_type fun(random_type x){return x;}
    int main(){
        std::cout << supports<decltype(&fun)(double)>::value << std::endl; // '0'
        std::cout << supports<decltype(&fun)(int)>::value << std::endl; // '0'
        std::cout << supports<decltype(&fun)(random_type)>::value << std::endl; // '1'
    }
    

    2) It can additionally detect if the result is convertible/comparable to a certain type, in this case double < double is supported but a compile-time false will be returned because the result is not the specified one.

    std::cout << supports<std::equal_to<>(std::result_of<std::less<>(double, double)>::type, random_type)>::value << std::endl; // '0'
    

    Note: I just tried to compile the code with C++14 in http://melpon.org/wandbox/ and it didn't work. I think there is a problem with transparent operators (like std::less<>) in that implementation (clang++ 3.5 c++14), since when I implement my own less<> with automatic deduction it works well.

    0 讨论(0)
  • 2020-12-05 02:00

    @xDD is indeed correct, though his example is slightly erroneous.

    This compiles on ideone:

    #include <array>
    #include <iostream>
    
    struct Support {}; bool operator<(Support,Support) { return false; }
    struct DoesNotSupport{};
    
    template <class T>
    struct supports_less_than
    {
      template <typename U>
      static auto less_than_test(const U* u) -> decltype(*u < *u, char(0))
      { }
    
      static std::array<char, 2> less_than_test(...) { }
    
      static const bool value = (sizeof(less_than_test((T*)0)) == 1);
    };
    
    int main()
    {
      std::cout << std::boolalpha << supports_less_than<Support>::value << std::endl;
      std::cout << std::boolalpha <<
         supports_less_than<DoesNotSupport>::value << std::endl;
    }
    

    And results in:

    true
    false
    

    See it here in action.

    The point is that SFINAE only applies to template functions.

    0 讨论(0)
  • 2020-12-05 02:07

    You need to make your less_than_test function a template, since SFINAE stands for Substitution Failure Is Not An Error and there's no template function that can fail selection in your code.

    template <class T>
    struct supports_less_than
    {
        template <class U>
        static auto less_than_test(const U* u) -> decltype(*u < *u, char(0))
        { }
    
        static std::array<char, 2> less_than_test(...) { }
    
        static const bool value = (sizeof(less_than_test((T*)0)) == 1);
    };
    
    int main()
    {
        std::cout << std::boolalpha << supports_less_than<std::string>::value << endl;
    }
    
    0 讨论(0)
  • 2020-12-05 02:12

    Below simple code satisfies your requirement (if you don't want compile error):

    namespace supports {
      template<typename T>  // used if T doesn't have "operator <" associated
      const long long operator < (const T&, const T&);
    
      template <class T>
      struct less_than {
        T t;
        static const bool value = (sizeof(t < t) != sizeof(long long));
      };  
    }
    

    Usage:

    supports::less_than<std::string>::value ====> true;  // ok
    supports::less_than<Other>::value ====> false;  // ok: no error
    

    [Note: If you want compile error for classes not having operator < than it's very easy to generate with very few lines of code.]

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