问题描述
我意识到这是一个非常基本的问题,但我只是想确认我是否正确使用了 std::enable_if
,因为在尝试调用禁用功能。
考虑以下程序 (link),它预计不会编译:
#include <type_traits>
template <int Z=0> std::enable_if_t<Z> function () { }
int main () {
function();
}
g++ --std=c++17 -W -Wall -pedantic smelly_template.cpp -o build-c++17/smelly_template
smelly_template.cpp: In function ‘int main()’:
smelly_template.cpp:6:12: error: no matching function for call to ‘function()’
6 | function();
| ^
smelly_template.cpp:3:40: note: candidate: ‘template<int Z> std::enable_if_t<(Z != 0)> function()’
3 | template <int Z=0> std::enable_if_t<Z> function () { }
| ^~~~~~~~
smelly_template.cpp:3:40: note: template argument deduction/substitution Failed:
In file included from smelly_template.cpp:1:
/usr/include/c++/9/type_traits: In substitution of ‘template<bool _Cond,class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = (0 != 0); _Tp = void]’:
smelly_template.cpp:3:40: required by substitution of ‘template<int Z> std::enable_if_t<(Z != 0)> function() [with int Z = 0]’
smelly_template.cpp:6:12: required from here
/usr/include/c++/9/type_traits:2384:11: error: no type named ‘type’ in ‘struct std::enable_if<false,void>’
2384 | using enable_if_t = typename enable_if<_Cond,_Tp>::type;
| ^~~~~~~~~~~
我的问题很简单:这些是我在正确使用 enable_if
时应该看到的错误消息吗(例如,它们只是记录了编译器试图做的事情),还是我搞砸了什么?起来了吗?
我不确定的原因是:
- 我期望错误输出仅是“没有匹配的函数可以调用'function()'”,然后停在那里,好像我正在调用一个没有调用的vanilla函数存在。 (不过,我不知道这种期望从何而来。)
- 在我读过的所有关于
enable_if
的帖子、教程和文档中,每次有人遇到“在 enable_if 中没有名为 'type' 的类型”错误时,似乎总是因为他们正在做一些错误的东西(只是粗略搜索:example、example、example,列表还在继续)。
所以我想知道是否我做错了什么,因为我也看到了“没有名为‘type’的类型”错误。
虽然我确实看到了最终的预期行为——编译失败——但我现在更关心的是完全正确的用法,而不是简单地以某种形式满足程序要求(编译失败)。
解决方法
std::enable_if_t
是一个模板,如果条件为假,它会导致替换失败。由于 SFINAE,当它在重载解析期间发生时,这不会导致程序格式错误。您将一个 false 值作为默认值传递给它,因此对于对 function()
的调用,如果没有任何额外指定的模板参数,重载解析将失败。
如果您将 template <int Z = 0>
部分更改为 int Z = 1
,那么我希望代码能够编译。
问题的第二部分更多:这些其他错误是预期的吗?
smelly_template.cpp:3:40: note: candidate: ‘template<int Z> std::enable_if_t<(Z != 0)> function()’
3 | template <int Z=0> std::enable_if_t<Z> function () { }
| ^~~~~~~~
smelly_template.cpp:3:40: note: template argument deduction/substitution failed:
In file included from smelly_template.cpp:1:
是的,只要重载解析失败,编译器就会尝试通过显示它尝试过的内容来帮助您。 gcc 和 clang 的现代版本将向您展示每个可用的重载以及为什么不能使用它。在这种情况下,它解释了为什么重载解析失败了它尝试的一个重载。重载解析失败时的这些类型的错误对于大型程序非常很有帮助。
,我原以为错误输出只是“没有匹配的函数可以调用‘function()’”,然后就停在那里,就好像我在调用一个不存在的普通函数一样。
正是你得到的:“错误:没有匹配的函数来调用‘function()’”
您还会收到另一个额外的“注释”,以防万一您期望禁用的 function()
可用,通知您替换失败。
在我读过的有关 enable_if 的所有帖子、教程和文档中,每次有人遇到“在 enable_if 中没有名为 'type' 的类型”错误时,似乎总是因为他们做的事情不正确>
不一定。
有时您希望拥有相同功能的替代版本;例如
template <int Z>
std::enable_if_t<Z==0> function ()
{ std::cout << "version zero" << std::endl; }
template <int Z>
std::enable_if_t<Z!=0> function ()
{ std::cout << "version non zero" << std::endl; }
,
在过去的某个时候,我为 GCC 错误输出编写了一个解析器。现在,我推荐 --fdiagnostics-format=json
,但它也有/有缺点。无论如何,这是错误信息的解构:
# First,the location of the following message
smelly_template.cpp: In function ‘int main()’:
# Then,the main message
smelly_template.cpp:6:12: error: no matching function for call to ‘function()’
# followed by the code location where it happened
6 | function();
| ^
# Now,we get "child diagnostics". They still belong to the same error.
# The child diagnostics explain why the parent diagnostic happened
# First child diagnostic: we have an overload resolution candidate,but it's not viable
smelly_template.cpp:3:40: note: candidate: ‘template<int Z> std::enable_if_t<(Z != 0)> function()’
3 | template <int Z=0> std::enable_if_t<Z> function () { }
| ^~~~~~~~
# Child of the first child diagnostic. You can see this by the number of spaces after `note: ` :)
# The overload candidate was removed from the overload set because of substitution failure
smelly_template.cpp:3:40: note: template argument deduction/substitution failed:
# Now we get a template instantiation stack for the grandchild diagnostic
# Starting with a file
In file included from smelly_template.cpp:1:
# Backwards from deepest template instantiation
/usr/include/c++/9/type_traits: In substitution of ‘template<bool _Cond,class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = (0 != 0); _Tp = void]’:
# Next instantiation level
smelly_template.cpp:3:40: required by substitution of ‘template<int Z> std::enable_if_t<(Z != 0)> function() [with int Z = 0]’
# Next instantiation level
smelly_template.cpp:6:12: required from here
# Why did the substitution fail? (reason for the grandchild diagnostic)
/usr/include/c++/9/type_traits:2384:11: error: no type named ‘type’ in ‘struct std::enable_if<false,void>’
2384 | using enable_if_t = typename enable_if<_Cond,_Tp>::type;
| ^~~~~~~~~~~
另见 -fdiagnostics-format=json
的输出。这是不完美的,因为它只使用了一层嵌套,尽管逻辑错误结构有更多层。
[
{
"kind": "error","children": [
{
"kind": "note","locations": [
{
"finish": {
"line": 3,"file": "prog.cc","column": 47
},"caret": {
"line": 3,"column": 40
}
}
],"message": "candidate: 'template<int Z> std::enable_if_t<(Z != 0)> function()'"
},{
"kind": "note","message": " template argument deduction/substitution failed:"
},{
"kind": "error","locations": [
{
"finish": {
"line": 2384,"file": "/opt/wandbox/gcc-9.3.0/include/c++/9.3.0/type_traits","column": 21
},"caret": {
"line": 2384,"column": 11
}
}
],"message": "no type named 'type' in 'struct std::enable_if<false,void>'"
}
],"locations": [
{
"caret": {
"line": 6,"column": 15
}
}
],"message": "no matching function for call to 'function()'"
}
]
,
您必须了解 SFINAE 不是也不是旨在禁用/启用模板的语言设计。这是模板系统和语言工作方式的副作用。人们发现哦,我们可以通过这种方式使用模板来禁用/启用模板专业化。这就是为什么您在使用 SFINAE 时遇到的错误可能不是最直接的。我见过的一些编译器专门处理 std::enable_if
并得到更好的错误消息,例如“模板特化被 enable_if
禁用,因为...”
主要为此目的而设计的语言特性是 C++20 的概念。