为什么我会同时收到一个函数被使用但未定义和定义但未使用的警告?

4

我遇到了一对非常奇怪的编译器警告,看起来相互矛盾。以下是我写的代码:

#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 函数中的函数前向声明被解释为不同于出现在未命名命名空间中的后续函数有关,但如果是这样,我不确定为什么最终会链接并且为什么我会收到这些警告。


你不能在lambda内部前向声明theRealCallback()。将其移到lambda上方即可解决问题。 - lakeweb
我明天早上回来。你的帖子非常有趣。高手会参与并弄清楚这个问题。我有一种感觉,你的编译器对不应该容忍的东西很宽容。但是,不想像律师那样......晚安。 - lakeweb
1个回答

4
我认为开放的CWG issue 2058与此相关,可以决定您的程序是否格式良好。
根据当前标准的措辞,我认为您的程序不符合规范。根据C++17标准(最终草案):根据[basic.link]/6,您lambda中的声明将声明具有外部链接的theRealCallback,因为它是块作用域中的函数声明,不匹配其他已声明的实体。同时,根据[basic.link]/4.2theRealCallback的第二个声明具有内部链接,因为它是未命名命名空间中函数的命名空间作用域声明。
根据[basic.link]/6,如果程序在同一翻译单元中声明具有内部和外部链接的实体,则程序不符合规范。然而,这种不规范性是最近才添加的,作为对CWG issue 426的解决方案。
如CWG问题426的注释所述,根据[basic.link]/9,声明仅在它们具有相同链接时才引用同一实体,这意味着其解决方案中的非正常形式条件不适用。
因此,如果我们严格解释这一点,那么程序确实有两个独立的函数void theRealCallback(),一个具有外部链接,一个具有内部链接。具有内部链接的函数具有定义,但具有外部链接的函数没有定义。如果是这种情况,程序将违反一个定义规则,因为lambda中的调用theRealCallback();使用了没有定义的具有外部链接的函数。这将使程序非正常形式,无需诊断。
虽然这可能是通过严格阅读标准的正确解释,但我认为CWG问题426的解决方案应该适用于这里,因为以上解释永远不会适用。我不知道为什么在解决方案中未修复提到的问题。
如果CWG问题2058得到解决,声明块作用域将匹配封闭命名空间的链接,则程序将是良好的,并且theRealCallback将具有内部链接。

您可以看到,如果使用-pedantic-errors标志来严格解释标准,GCC会认为程序存在问题,这将导致它发出错误而不是警告。


当然,解决这个问题最简单的方法是在命名空间范围内提前声明void theRealCallback();作为lambda之外的前置声明,在这种情况下,链接肯定是内部的,任何块范围的声明都将引用该声明,承担其链接。
如果命名空间有名称,则这不是一个问题,因为命名空间作用域声明也将具有外部链接。

我得出了类似的结论,只是名称实际上并没有表示相同的函数,参见 [basic.link]/10。因此,在 [basic.link]/7 中的规则不适用; 这只是一个ODR违反。问题426的说明指出了这一点,但似乎该点从未解决。 - aschepler
@aschepler 我已经添加了一个关于这个的段落。请让我知道这是否与您得出的结论不同。 - walnut
谢谢你的澄清。你帮我省了很多功课。 - lakeweb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接