这些是 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 时应该看到的错误消息吗(例如,它们只是说明编译器试图做什么),或者我搞砸了吗?

我不确定的原因是:

所以我想知道 我是否 做错了什么,因为我也看到了“没有名为 '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 的概念就是主要为此目的而设计的语言特性。