为什么`std :: enable_if`需要默认值?

时间:2019-05-19 15:56:25

标签: c++ c++11 templates sfinae

为什么在这种::type = 0用法中必须使用默认值(std::enable_if)?

我看到了一些例子,没有它就可以工作。例如https://foonathan.net/blog/2015/11/30/overload-resolution-4.html

#include<iostream>
#include<type_traits>

template <typename T,
          typename std::enable_if<std::is_integral<T>::value, T>::type = 0>
void do_stuff(T t) {
    std::cout << "do_stuff integral\n";
}

template <typename T,
          typename std::enable_if<std::is_class<T>::value, T>::type = 0>
void do_stuff(T t) {
    std::cout << "do_stuff class\n";
}

int main()
{
    do_stuff(32);
    return 0;
}

我收到错误消息:

temp.cpp:6:6: note:   template argument deduction/substitution failed:
temp.cpp:18:13: note:   couldn't deduce template parameter ‘<anonymous>’

应该推断为

template <template T, int>
void do_stuff(T t)

这是有效的代码。 我做错了什么? ( gcc 版本 7.4.0

2 个答案:

答案 0 :(得分:1)

我无法重现您的错误;无论如何,我在使用类调用do_stuff()时遇到错误。例如

do_stuff(std::string{"abc"})

这是因为do_stuff()成为do_stuff<std::string, std::string = 0>(),并且模板值不能为std::string类型(并且默认值为零)。

建议:重写将int用作第二位置值类型的函数

template <typename T, // .....................................VVV  int, not T
          typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void do_stuff(T t) {
    std::cout << "do_stuff integral\n";
}

template <typename T, // ..................................VVV  int, not T
          typename std::enable_if<std::is_class<T>::value, int>::type = 0>
void do_stuff(T t) {
    std::cout << "do_stuff class\n";
}

这样,通过调用do_stuff(std::string{"abc"}),您可以启用do_stuff<std::string, int = 0>()

答案 1 :(得分:1)

编译器清楚地告诉您问题出在哪里:在模板声明中,您指定了一个无法推导的额外模板非类型参数。您如何期望编译器为该非类型参数推断出适当的值?从哪里来?

这正是上述使用std::enable_if的技术需要默认参数的原因。这是一个伪参数,因此默认参数值无关紧要(0是自然选择)。

您可以将示例简化为

template <typename T, T x> 
void foo(T t) {}

int main()
{
  foo(42);
}

生产

error: no matching function for call to 'foo(int)'
note:   template argument deduction/substitution failed:
note:   couldn't deduce template parameter 'x'

编译器可以推导T是什么(T == int),但是编译器无法推导x的参数。

您的代码完全相同,除了您的第二个模板参数未命名(无需为虚拟参数命名)。


从您的评论来看,您似乎对代码中第二个参数的声明中存在关键字typename感到困惑,这使您相信第二个参数也是 type < / strong>参数。后者是不正确的。

请注意,在第二个参数的声明关键字typename中,的作用完全不同。该关键字只是消除了

的语义歧义
std::enable_if<std::is_class<T>::value, T>::type

它告诉编译器嵌套名称type实际上表示类型的名称,而不是其他名称。 (您可以在Why do we need typename here?Where and why do I have to put the "template" and "typename" keywords?上了解typename的用法)

使用typename不会将模板的第二个参数转换为 type 参数。模板的第二个参数仍然是非类型参数。

这是另一个简化的示例,用于说明代码中发生的情况

struct S { typedef int nested_type; };

template <typename T, typename T::nested_type x>
void bar(T t)
{}

int main()
{
  S s;
  bar<S, 42>(s);
}

请注意,即使第二个参数的声明以typename开头,它仍然声明非类型参数。