【问题标题】:Why the default value is needed for `std::enable_if`?为什么 `std::enable_if` 需要默认值?
【发布时间】:2019-05-19 15:56:24
【问题描述】:

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

我看到了一些例子,没有它也能正常工作。例如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)

【问题讨论】:

  • 在您引用的示例中,调用enable_if 默认参数。它看起来像这样:typename = std::enable_if&lt;...&gt;::type。它是给定默认值的模板类型参数(省略其名称)。在您的代码中,您有一个std::enable_if&lt;...&gt;::type 类型的非类型模板参数,无法推断它。
  • @IgorTandetnik 当我这样做时,我得到: temp.cpp:13:6: error: redefinition of 'template void do_stuff(T)' void do_stuff(T t) { ^~~~~~~~ temp.cpp:7:6: 注意:'template void do_stuff(T)' 之前在这里声明过 void do_stuff(T t) {
  • 文中的例子也没有编译通过。可能是时候寻找更好的文章了。
  • @IgorTandetnik。这是 Jonathan Müller 的帖子 (twitter.com/foonathan)。它应该工作:)
  • @DanielLangr。是的,这是工作示例,我在问为什么 ::type = 0 必须在那里?尝试删除它。

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


【解决方案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 的参数。

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


从你的cmets来看,你似乎对代码中第二个参数声明中关键字typename的存在感到困惑,这让你认为第二个参数也是一个类型 参数。后者不正确。

请注意,在第二个参数的声明关键字typename 中使用了完全不同的角色。这个关键字只是消除了语义的歧义

std::enable_if<std::is_class<T>::value, T>::type

它告诉编译器嵌套名称type 实际上代表一个类型 的名称,而不是别的东西。 (您可以在此处阅读typename 的用法:Why do we need typename here?Where and why do I have to put the "template" and "typename" keywords?

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 开头,它仍然声明了一个非类型参数。

【讨论】:

  • 看来你是对的。我仍然缺少一些东西。我的理解是,如果编译器将第一个 T 推导出为 int,那么第二个 T 也是 int,所以 -> x 是 int。 “x 的参数”是什么意思,输入?谢谢澄清
  • @MiCha:您似乎错过了这样一个事实,即在这两种情况下,第一个模板参数都是 type 参数,而第二个是 non-type 参数(“值”参数)。在我的示例参数T 代表int,但x 不代表intx 是一个 value 参数,它代表 int 类型的 some value。编译器应该使用什么值?编译器不知道。
  • @AnT 是的,你是对的,但这就是你和我的例子之间的区别。在我的例子中,第二个模板参数也是类型(关键字类型名),而 enable_if::type 是 T 的别名。所以如果 T 是 int 那么 ::type 是 int,那么为什么类型参数还需要 默认值?谢谢
  • @MiCha:不,不,不,不,不。你的例子和我的完全一样。您只是误解了第二个参数中typename 一词的含义。不,它意味着这是一个类型参数。你的第二个参数是一个值参数,就像我的一样。查看更新的答案。
  • 就是这样!谢谢!
【解决方案2】:

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

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

这是因为 do_stuff() 变为 do_stuff&lt;std::string, std::string = 0&gt;() 并且模板值不能是 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&lt;std::string, int = 0&gt;(),这是可以接受的。

【讨论】:

  • 是的,最好的是:template &lt;typename T, typename std::enable_if&lt;std::is_integral&lt;T&gt;::value, T&gt;::type* = nullptr&gt;。问题是,为什么这个 nullptr 必须在那里?
  • @MiCha 那么,这个非类型模板参数的值应该来自哪里?你基本上有template &lt;typename T, void* x&gt; void do_stuff(T); - 编译器需要x的值
  • @MiCha - 不需要使用T(直接或作为指针); int = 0 完美运行。无论如何,= nullptr(或= 0,在我的示例中)是必要的,以避免显式它;否则你必须打电话给do_stuff&lt;int, nullptr&gt;(42) 而不是do_stuff(42);
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-07
  • 2012-07-28
  • 1970-01-01
相关资源
最近更新 更多