这些是 std::enable_if 的预期错误,还是我使用不当?

问题描述

我意识到这是一个非常基本的问题,但我只是想确认我是否正确使用了 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 时应该看到的错误消息吗(例如,它们只是记录了编译器试图做的事情),还是我搞砸了什么?起来了吗?

我不确定的原因是:

  • 我期望错误输出是“没有匹配的函数可以调用'function()'”,然后停在那里,好像我正在调用一个没有调用的vanilla函数存在。 (不过,我不知道这种期望从何而来。)
  • 在我读过的所有关于 enable_if 的帖子、教程和文档中,每次有人遇到“在 enable_if 中没有名为 'type' 的类型”错误时,似乎总是因为他们正在做一些错误的东西(只是粗略搜索exampleexampleexample,列表还在继续)。

所以我想知道是否做错了什么,因为我也看到了“没有名为‘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 的概念。