std :: enable_if和通用引用在用法上的差异

时间:2018-10-05 14:23:19

标签: c++ templates c++14 enable-if forwarding-reference

我试图更好地理解通用引用,并且std::enable_if更好,但是我对代码中发生的事情有些犹豫。

首先,我注意到人们似乎以两种不同的方式使用std::enable_if

  1. template<typename T, std::enable_if<condition, T>::type* = nullptr>或类似的东西。

  2. template<typename T> std::enable_if_t<condition, T> myfunc() {...}或类似的东西。

我了解第二个事件的发生,但是我对为什么有人会使用第一个感到困惑。除了向模板添加另一个参数之外,这还能实现什么?是SFINAE的东西吗?

在使用enable_if时,我也坚持使用通用引用。这是我的代码和得到的结果。请注意,我使用的是“ Is it possible to print a variable's type in standard C++?”中的Howard Hinnant的类型打印代码,为简洁起见,在此将省略。

无论如何,函数conditionless似乎可以正常工作。

我对is_integraldecay感到非常困惑,您可以在main的开头看到它们。我得到了输出:

true: unsigned long false: unsigned long false: unsigned long false: unsigned long

我不知道为什么后三个错误。

然后我有一个问题(在下面的源代码中标记为1和2),当以上述两种方式之一使用enable_if时,它们在接受整数或浮点类型的左值时拒绝编译

为简洁起见,省略了标题和类型的打印代码:

template<typename T>
void conditionless(T&& val) {
    std::cout << "conditionless(" << val << ")\n";
}

template<typename T, typename std::enable_if<std::is_integral_v<T>, T>::type* = nullptr>
void outputIntType(T&& val) {
    std::cout << "outputIntType(" << val << ")\n";
}

template<typename T>
typename std::enable_if_t<std::is_floating_point_v<T>>
outputFloatType(T&& val) {
    std::cout << "outputFloatType(" << val << ")\n";
}

int main() {

    size_t sz = 1;
    size_t &ref = sz;

    // All of these report as having type "unsigned long", but for some reason, the first reports true for is_integral, and
    // the other three report false.
    std::cout << std::boolalpha << std::is_integral_v<decltype(sz)> << ": " << type_name<decltype(sz)>() << '\n';
    std::cout << std::boolalpha << std::is_integral_v<std::decay<decltype(sz)>> << ": " << type_name<std::decay<decltype(sz)>::type>() << '\n';
    std::cout << std::boolalpha << std::is_integral_v<decltype(ref)> << ": " << type_name<decltype(sz)>() << '\n';
    std::cout << std::boolalpha << std::is_integral_v<std::decay<decltype(ref)>> << ": " << type_name<std::decay<decltype(ref)>::type>() <<'\n';

    //  This works fine.
    conditionless(sz);
    conditionless(2UL);
    conditionless(2L + 1);

    // ******* 1 *******
    // This fails and claims no matching function call to outputIntType(size_t&)
    // template argument deduction / substitution failed:
    // error: no type named 'type' in 'struct std::enable_if<false, long unisgned int&>'
    // I'm particularly confused about why the is_integral evaluates to false.
    //outputIntType(sz); 

    // These work fine.
    outputIntType(2UL);
    outputIntType(2L + 1);

    double pi = 3.1415926535;

    // These work fine.
    conditionless(pi);
    conditionless(2 * pi);
    conditionless(0.00000001);

    // ******* 2 *******
    // This fails as well:
    // main.cpp: In function 'int main()':
    // error: no matching function for call to 'outputFloatType(double&)'
    // note: candidate: 'template<class T> std::enable_if_t<is_floating_point_v<T> > outputFloatType(T&&)'
    // template argument deduction/substitution failed:
    // outputFloatType(pi);

    // These work fine.
    outputFloatType(2 * pi);
    outputFloatType(0.00000001);
}

任何人都可以给我介绍enable_if的两种不同用法以及为什么我的带有enable_if的代码拒绝接受左值的见解,将不胜感激。

1 个答案:

答案 0 :(得分:4)

  

我正在尝试理解通用参考书

不鼓励使用该术语。正式术语是“转发参考”


  

我了解第二个事件的发生,但是我对为什么有人会使用第一个感到困惑。除了向模板添加另一个参数之外,这还能实现什么?是SFINAE的东西吗?

如果enable_if_t<B, T>,所有T都将求值为B == true,否则将生成无效代码。替换期间生成的无效代码不会导致编译错误(SFINAE)。

enable_if_t的出现位置并不重要,只要它受替换步骤的影响即可(例如可以位于返回类型,参数列表,模板参数列表等)。


  

我不知道为什么后三个错误。

您忘记在::type转换中访问std::decay。您正在比较特征本身,而不是结果。


  

当接受整数或浮点类型的左值时,它们拒绝编译。

在标准中有一个关于推论转发引用的特殊规则。给定转发参考参数T&&,如果使用 lvalue 调用函数,则将推论T作为左值参考

您需要在特征中考虑这一点:

typename std::enable_if_t<std::is_floating_point_v<std::remove_reference_t<T>>>