为什么我会收到函数已使用但未定义和已定义但未使用的警告?

Why do I get warnings both that a function is used but not defined and defined but not used?

我遇到了一对不寻常的编译器警告,它们似乎相互矛盾。这是我写的代码:

#include <functional>
#include <iostream>

namespace {
  std::function<void()> callback = [] {
    void theRealCallback(); // Forward-declare theRealCallback
    theRealCallback();      // Invoke theRealCallback
  };

  void theRealCallback() {
    std::cout << "theRealCallback was called." << std::endl;
  }
}

int main() {
  callback();
}

换句话说,我在一个未命名的命名空间中定义了一个 lambda 函数,它向前声明了一个稍后在该未命名的命名空间中定义的函数。

当我用 g++ 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1) 编译这段代码时,我得到了这两个警告:

CompilerWarningsNamespace.cpp:6:10: warning: ‘void {anonymous}::theRealCallback()’ used but never defined
     void theRealCallback();
          ^~~~~~~~~~~~~~~
CompilerWarningsNamespace.cpp:10:8: warning: ‘void {anonymous}::theRealCallback()’ defined but not used [-Wunused-function]
   void theRealCallback() {
        ^~~~~~~~~~~~~~~

这很奇怪,因为第一个警告说我正在使用一个函数但没有定义它,而第二个警告说我正在定义一个函数但没有使用它。

运行 这个程序确实产生了输出

theRealCallback was called.

程序正常结束。

有趣的是,如果我不使用未命名的命名空间,而是给命名空间命名,这些警告就会消失,如下所示:

#include <functional>
#include <iostream>

namespace NamedNamespace {
  std::function<void()> callback = [] {
    void theRealCallback();
    theRealCallback();
  };

  void theRealCallback() {
    std::cout << "theRealCallback was called." << std::endl;
  }
}

int main() {
  NamedNamespace::callback();
}

代码的修改版本与原始代码一样,打印出一条消息,表明 theRealCallback 已被调用。

有人可以解释为什么我会收到这些警告吗?我的猜测是,这与 lambda 函数中函数的前向声明有关,因为它被解释为不同于出现在未命名命名空间中的后期函数的东西,但如果是这种情况,我不确定我是否看到为什么这个链接最终会出现以及为什么我会收到这些警告。

我认为 open CWG issue 2058 在这里与决定你的程序是否合式有关。


根据标准的当前措辞,我认为您的程序格式错误。

基于C++17标准(最终草案):

根据 [basic.link]/6,您的 lambda 中的声明将使用 外部链接 声明 theRealCallback,因为它是块作用域内的函数声明,不与其他一些已声明的实体匹配。

同时根据 [basic.link]/4.2 theRealCallback 的第二个声明具有 内部链接 因为它是一个函数的名称空间范围声明未命名的命名空间。

根据 [basic.link]/6,如果一个程序在同一个翻译单元中声明一个具有内部和外部链接的实体,则该程序是格式错误的。不过,这种格式错误是最近才添加的,作为 CWG 问题 426.

的解决方案

如 CWG 问题 426 的注释中所述,根据 [basic.link]/9,声明仅引用同一实体,尽管它们具有相同的链接,这意味着其决议中的格式错误条件不会'不适用。

所以如果我们严格地解释这个,那么这个程序确实有两个独立的功能void theRealCallback(),一个是外部链接,一个是内部链接。内联的有定义,外联的没有。如果是这种情况,则程序违反了单一定义规则,因为 lambda ODR 中的调用 theRealCallback(); 使用了没有定义的具有外部链接的函数。这会使程序格式错误,不需要诊断。

虽然这可能是严格阅读标准的正确解释,但我认为 CWG 问题 426 的解决方案应该适用于此,因为按照上面的解释它永远不会适用。我不知道为什么提到的问题没有在决议中得到解决。


如果 CWG issue 2058 解析为块范围内的声明将匹配封闭名称空间的链接,那么程序将是格式良好的并且 theRealCallback 将具有内部链接。


您可以看到,如果通过添加 -pedantic-errors 标志严格解释标准,GCC 认为程序格式错误,这将导致它发出错误而不是警告。


当然,解决此问题的最简单方法是在命名空间范围内的 lambda 外部转发声明 void theRealCallback();,在这种情况下,链接绝对是内部的,任何块范围声明都将引用该声明,采用关于它的链接。


如果命名空间被命名,这不是问题,因为命名空间范围声明也将具有外部链接。