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 函数中函数的前向声明有关,因为它被解释为出现在未命名命名空间中的后一个函数之外的东西,但如果是这种情况,我不确定我看到为什么这个链接最终以及为什么我收到这些警告。

4

1 回答 1

4

我认为开放的CWG 问题 2058在这里与决定您的程序是否格式正确有关。


按照标准的当前措辞,我认为您的程序格式不正确。

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

根据[basic.link]/6,您的 lambda 中的声明将声明theRealCallbackexternal links ,因为它是块范围内的函数声明,与其他已声明的实体不匹配。

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

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


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

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

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


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


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


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


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

于 2020-01-31T03:17:15.450 回答