我遇到了一对不寻常的编译器警告,它们似乎相互矛盾。这是我编写的代码:
#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 函数中函数的前向声明有关,该函数被解释为未命名命名空间中出现的后一个函数以外的其他函数,但如果是这种情况,我不确定我明白为什么这最终链接以及为什么我会收到这些警告。
我认为开放的 CWG 问题 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 第 2058 期被解决为块范围的声明将与封闭命名空间的链接匹配,那么程序将格式良好,并且theRealCallback
将具有内部链接。
您可以看到,如果通过添加-pedantic-errors
标志严格解释标准,GCC 认为程序格式不正确,这将导致它发出错误而不是警告。
当然,解决此问题的最简单解决方法是在命名空间范围内转发声明 lambda 之外的void theRealCallback();
,在这种情况下,链接肯定是内部的,任何块范围声明都会引用该声明,并承担其链接。
如果命名空间已命名,这不是问题,因为命名空间范围声明也将具有外部链接。