使用decltype/SFINAE检测操作员支持

Cha*_*l72 15 c++ decltype sfinae c++11

(有些)过时的文章探讨了decltype与SFINAE一起使用的方法,以检测某种类型是否支持某些运算符,例如==<.

以下是检测类是否支持<运算符的示例代码:

template <class T>
struct supports_less_than
{
    static auto less_than_test(const T* t) -> decltype(*t < *t, 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;
}
Run Code Online (Sandbox Code Playgroud)

这输出true,因为当然std::string支持<操作员.但是,如果我尝试将它与支持<运算符的类一起使用,我会收到编译器错误:

error: no match for ‘operator<’ in ‘* t < * t’
Run Code Online (Sandbox Code Playgroud)

所以SFINAE不在这里工作.我在GCC 4.4和GCC 4.6上试过这个,两者都表现出相同的行为.那么,是否可以以这种方式使用SFINAE来检测某种类型是否支持某些表达式?

alf*_*lfC 16

在C++ 11中,我找到的最简单最通用的解决方案是:

#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'
}
Run Code Online (Sandbox Code Playgroud)

适用于g++ 4.8.1clang++ 3.3


任意运营商的更通用解决方案(UPDATE 2014)

有一个更通用的解决方案利用了这样一个事实,即所有内置运算符也可以通过STD运算符包装器(例如std::less(二进制)或std::negate(一元))访问(并且可以专门地).

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>()...)){};
Run Code Online (Sandbox Code Playgroud)

这可以以一种非常通用的方式使用,尤其是在C++ 14中,其中类型推导被延迟到运算符包装器调用("透明运算符").

对于二元运算符,它可以用作:

#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'
}
Run Code Online (Sandbox Code Playgroud)

对于一元运算符:

#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'
}
Run Code Online (Sandbox Code Playgroud)

(使用C++ 11标准库有点复杂,因为decltype(std::less<random_type>()(...))即使没有定义操作也没有实现失败random_type,可以在C++ 11中实现手动透明操作符,这是C++中的标准操作符. )

语法非常流畅.我希望标准中采用这样的东西.


两个扩展:

1)它可以检测原始功能应用程序:

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'
}
Run Code Online (Sandbox Code Playgroud)

2)它还可以检测结果是否可转换/与某种类型相当,在这种情况下double < double是支持的,但是由于结果不是指定的结果,将返回编译时false.

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

注意:我只是尝试在http://melpon.org/wandbox/中使用C++ 14编译代码,但它不起作用.我认为std::less<>在该实现中有透明运算符(例如)的问题(clang ++ 3.5 c ++ 14),因为当我less<>使用自动演绎实现自己的运算符时,它运行良好.


xDD*_*xDD 9

您需要将less_than_test函数设置为模板,因为SFINAE代表替换失败不是错误,并且没有模板函数可能在代码中失败选择.

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;
}
Run Code Online (Sandbox Code Playgroud)


ild*_*arn 6

这是C++ 0x,我们不再需要sizeof基于技巧的了...; - ]

#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
    > { };
}
Run Code Online (Sandbox Code Playgroud)

(这是基于iammilind的回答,但不要求Toperator<返回类型是不同的尺寸long long,并且不要求T是默认施工的.)