std::enable_if 和通用引用的使用差异

Differences in uses of std::enable_if and universal references

我试图更好地理解通用引用 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 时,它们在接受整数或浮动的左值时拒绝编译点类型。

Headers 并为简洁起见省略了打印代码:

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 代码拒绝接受左值的任何见解,将不胜感激。

I'm trying to understand universal references

不鼓励使用该术语。官方术语是"forwarding references".


I understand what's happening in the second, but I'm confused about why anyone would use the first. What does that achieve except add another parameter to the template? Is it an SFINAE thing?

如果 B == trueenable_if_t<B, T> 所做的就是评估为 T,否则会生成 无效代码 。替换期间产生的无效代码不会导致编译错误 (SFINAE)。

enable_if_t 出现在哪里并不重要,只要它受替换步骤的影响(例如可以在 return 类型、参数列表、模板参数列表中,...) .


and I have no idea why the last three are false.

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


they refuse to compile when accepting an lvalue of an integral or floating point type.

标准中有一条关于扣除转发引用的特殊规则。给定转发引用参数 T&&,如果使用 lvalue[=42= 调用函数,T 将被推断为 lvalue reference ].

你需要在你的特质中考虑到这一点:

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