【问题标题】:Why do I get warnings both that a function is used but not defined and defined but not used?为什么我会收到警告说函数已使用但未定义和已定义但未使用?
【发布时间】:2020-05-16 16:47:14
【问题描述】:

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

#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()。将它移到上面,它应该可以工作。
  • 我早上回来。你的帖子最有趣。重量级人物会介入并解决这个问题。我有一种感觉,你的编译器正在原谅它不应该做的事情。但是没有做律师的事......晚安。

标签: c++ lambda g++ compiler-warnings unnamed-namespace


【解决方案1】:

我认为开放的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 ODR 中的调用 theRealCallback(); 使用了具有没有定义的外部链接的函数。这会使程序格式错误,不需要诊断。

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


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


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


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


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

【讨论】:

  • 我得出了类似的结论,除了名称实际上并不表示相同的功能,每个 [basic.link]/10。所以 [basic.link]/7 中的规则不适用;这只是违反 ODR。关于 issue 426 的注释指出了这一点,但似乎这一点从未得到解决。
  • @aschepler 我已经添加了一段关于此的内容。如果这与您的结论不同,请告诉我。
  • 感谢您的澄清。你帮我省了很多功课。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-19
  • 1970-01-01
  • 1970-01-01
  • 2023-01-07
  • 1970-01-01
相关资源
最近更新 更多