这些是 std::enable_if 的预期错误,还是我使用不当?
Are these the expected errors with std::enable_if, or am I using it incorrectly?
我知道这是一个非常基本的问题,但我只是想确认我是否正确使用了 std::enable_if
,因为我有点不确定尝试时“正确”的错误消息应该是什么调用禁用的函数。
考虑以下程序 (link),预计不会编译:
#include <type_traits>
template <int Z=0> std::enable_if_t<Z> function () { }
int main () {
function();
}
GCC 9在C++17模式下输出的错误信息是:
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
时应该看到的错误消息吗(例如,它们只是说明编译器试图做什么),或者我搞砸了吗?
我不确定的原因是:
- 我期待 only 的错误输出是“没有匹配函数调用 'function()'”,然后就此停止,就好像我在调用 vanilla不存在的功能。 (虽然我不知道这种期望是从哪里来的。)
- 在我读过的关于
enable_if
的所有帖子、教程和文档中,每当有人遇到“enable_if 中没有名为 'type' 的类型”错误时,它总是似乎是因为他们做错了什么(只是粗略搜索:example, example, example,列表还在继续)。
所以我想知道 我是否 做错了什么,因为我也看到了“没有名为 'type' 的类型”错误。
虽然我确实看到了最终的预期行为——编译失败——但我现在更关心完全正确的用法,而不是简单地以某种形式满足程序要求(编译失败)。
std::enable_if_t
是一个模板,它会在条件为假时导致替换失败。由于 SFINAE,当它发生在重载解析期间时,这不会导致程序格式错误。您将一个假值作为默认值传递给它,因此对于 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 将向您显示每个可用的重载以及不能使用的原因。在这种情况下,它解释了为什么重载解析在它尝试过的一个重载中失败了。重载解析失败时出现的这类错误在大型程序中非常有用。
I was expecting the error output to only be "no matching function for call to 'function()'", and to stop there, as if I was calling a vanilla function that didn't exist.
您得到的正是:“错误:没有匹配函数来调用‘function()’”
您还会收到另一个额外的“提示”,以防万一您希望禁用 function()
可用,它会通知您替换失败。
In all the posts, tutorials, and documentation I've read concerning enable_if, every time somebody encounters the "no type named 'type' in enable_if" error, it always seems to be because they're doing something incorrectly
不一定。
有时您希望拥有相同功能的替代版本;例如
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
,但它 has/had 也有缺点。不管怎样,这里是对错误信息的解构:
# 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,
"file": "prog.cc",
"column": 40
}
}
],
"message": "candidate: 'template<int Z> std::enable_if_t<(Z != 0)> function()'"
},
{
"kind": "note",
"locations": [
{
"finish": {
"line": 3,
"file": "prog.cc",
"column": 47
},
"caret": {
"line": 3,
"file": "prog.cc",
"column": 40
}
}
],
"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,
"file": "/opt/wandbox/gcc-9.3.0/include/c++/9.3.0/type_traits",
"column": 11
}
}
],
"message": "no type named 'type' in 'struct std::enable_if<false, void>'"
}
],
"locations": [
{
"caret": {
"line": 6,
"file": "prog.cc",
"column": 15
}
}
],
"message": "no matching function for call to 'function()'"
}
]
您必须了解 SFINAE 过去不是,现在也不是以 disabling/enabling 模板为目的的语言设计。这是模板系统和语言工作方式的副作用。人们发现哦,我们可以通过这种方式使用模板来实现 disable/enable 模板专业化。这就是为什么您在使用 SFINAE 时遇到的错误可能不是最直接的原因。我见过的一些编译器会专门处理 std::enable_if
并获得更好的错误消息,例如“模板特化被 enable_if
禁用,因为...”
C++20 的概念就是主要为此目的而设计的语言特性。
我知道这是一个非常基本的问题,但我只是想确认我是否正确使用了 std::enable_if
,因为我有点不确定尝试时“正确”的错误消息应该是什么调用禁用的函数。
考虑以下程序 (link),预计不会编译:
#include <type_traits>
template <int Z=0> std::enable_if_t<Z> function () { }
int main () {
function();
}
GCC 9在C++17模式下输出的错误信息是:
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
时应该看到的错误消息吗(例如,它们只是说明编译器试图做什么),或者我搞砸了吗?
我不确定的原因是:
- 我期待 only 的错误输出是“没有匹配函数调用 'function()'”,然后就此停止,就好像我在调用 vanilla不存在的功能。 (虽然我不知道这种期望是从哪里来的。)
- 在我读过的关于
enable_if
的所有帖子、教程和文档中,每当有人遇到“enable_if 中没有名为 'type' 的类型”错误时,它总是似乎是因为他们做错了什么(只是粗略搜索:example, example, example,列表还在继续)。
所以我想知道 我是否 做错了什么,因为我也看到了“没有名为 'type' 的类型”错误。
虽然我确实看到了最终的预期行为——编译失败——但我现在更关心完全正确的用法,而不是简单地以某种形式满足程序要求(编译失败)。
std::enable_if_t
是一个模板,它会在条件为假时导致替换失败。由于 SFINAE,当它发生在重载解析期间时,这不会导致程序格式错误。您将一个假值作为默认值传递给它,因此对于 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 将向您显示每个可用的重载以及不能使用的原因。在这种情况下,它解释了为什么重载解析在它尝试过的一个重载中失败了。重载解析失败时出现的这类错误在大型程序中非常有用。
I was expecting the error output to only be "no matching function for call to 'function()'", and to stop there, as if I was calling a vanilla function that didn't exist.
您得到的正是:“错误:没有匹配函数来调用‘function()’”
您还会收到另一个额外的“提示”,以防万一您希望禁用 function()
可用,它会通知您替换失败。
In all the posts, tutorials, and documentation I've read concerning enable_if, every time somebody encounters the "no type named 'type' in enable_if" error, it always seems to be because they're doing something incorrectly
不一定。
有时您希望拥有相同功能的替代版本;例如
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
,但它 has/had 也有缺点。不管怎样,这里是对错误信息的解构:
# 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,
"file": "prog.cc",
"column": 40
}
}
],
"message": "candidate: 'template<int Z> std::enable_if_t<(Z != 0)> function()'"
},
{
"kind": "note",
"locations": [
{
"finish": {
"line": 3,
"file": "prog.cc",
"column": 47
},
"caret": {
"line": 3,
"file": "prog.cc",
"column": 40
}
}
],
"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,
"file": "/opt/wandbox/gcc-9.3.0/include/c++/9.3.0/type_traits",
"column": 11
}
}
],
"message": "no type named 'type' in 'struct std::enable_if<false, void>'"
}
],
"locations": [
{
"caret": {
"line": 6,
"file": "prog.cc",
"column": 15
}
}
],
"message": "no matching function for call to 'function()'"
}
]
您必须了解 SFINAE 过去不是,现在也不是以 disabling/enabling 模板为目的的语言设计。这是模板系统和语言工作方式的副作用。人们发现哦,我们可以通过这种方式使用模板来实现 disable/enable 模板专业化。这就是为什么您在使用 SFINAE 时遇到的错误可能不是最直接的原因。我见过的一些编译器会专门处理 std::enable_if
并获得更好的错误消息,例如“模板特化被 enable_if
禁用,因为...”
C++20 的概念就是主要为此目的而设计的语言特性。