我遇到了一对看起来相互矛盾的异常的编译器警告。这是我编写的代码:
#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();
}
与原始代码一样,代码的修改版本会打印出一条消息,指出已调用[C0]。
有人可以解释为什么我收到这些警告吗?我的猜测是,这与lambda函数中的函数的前向声明有关,后者被解释为与未命名空间中出现的更高版本的函数不同,但如果是这种情况,我不确定我是否看到为什么这最终会联系在一起,为什么我会收到这些警告。
我认为此处的theRealCallback
开头与决定程序是否格式正确相关。
根据当前的标准措辞,我认为您的程序格式不正确。
基于C ++ 17标准(最终草案):
根据CWG issue 2058,您的lambda中的声明将使用外部链接
声明[basic.link]/6,因为它是块范围内的函数声明,与其他已经声明的实体不匹配。同时根据theRealCallback
,[basic.link]/4.2的第二个声明具有内部链接
根据theRealCallback
,如果程序声明同一翻译单元中具有内部和外部链接的实体,则该程序格式错误。不过,这种格式错误只是在最近才添加,作为[basic.link]/6的分辨率。
正如CWG第426期的注释中所提到的,根据CWG issue 426,即使声明具有相同的链接,它们也仅引用相同的实体,这意味着其解析中的不良形式条件不适用。
因此,如果我们严格地解释这一点,那么该程序实际上具有两个独立的功能[basic.link]/9,一个具有外部链接,一个具有内部链接。具有内部链接的一个具有定义,但是具有外部链接的一个没有定义。如果是这种情况,则程序违反了一个定义规则,因为lambda ODR中的调用void theRealCallback()
使用具有外部链接的函数,该函数没有定义。这会使程序格式错误,不需要诊断。
虽然通过严格阅读标准可能是正确的解释,但我认为CWG第426号决议应在此处适用,因为上述解释永远不会适用。我不知道为什么决议中未解决上述问题。
应该解决theRealCallback();
说块范围内的声明将匹配封闭名称空间的链接,然后程序将具有良好的格式,并且CWG issue 2058将具有内部链接。
[您可以看到,如果通过添加theRealCallback
标志严格解释该标准,则GCC会将程序视为格式错误,这将导致该程序发出错误而不是警告。
当然,解决此问题的最简单方法是在名称空间范围的lambda外部转发声明-pedantic-errors
,在这种情况下,链接肯定是内部的,任何块范围声明都会引用该声明,并采用其链接。 >
如果命名空间被命名,这不是问题,因为命名空间作用域声明也将具有外部链接。
您不能在lambda中向前声明void theRealCallback();
。通过将声明移到lambda之上,它将起作用。另外,您无需使用theRealCallback()
。只需使用自动命名lambda。
std::function
这可能会使它更清晰。它具有相同的效果。
namespace {
void theRealCallback(); // Forward-declare theRealCallback
auto callback = [] {
theRealCallback(); // Invoke theRealCallback
};
void theRealCallback() {
std::cout << "theRealCallback was called." << std::endl;
}
}
int main() {
callback();
}