处理gcc的noexcept类型警告

35

考虑这个例子,来自bug 80985

template <class Func>
void call(Func f)
{
    f();
}

void func() noexcept { }

int main()
{
    call(func);
}

如果启用了所有警告来编译这段代码,就像你所做的那样,则会产生以下结果:

$ g++ -std=c++14 -Wall foo.cxx 
foo.cxx:2:6: warning: mangled name for ‘void call(Func) [with Func = void (*)() noexcept]’ will change in C++17 because the exception specification is part of a function type [-Wnoexcept-type]
 void call(Func f)
      ^~~~

我应该如何处理这个警告?应该怎么修复?


2
如果call完全是项目内部使用,那就没关系了。只有在两个不同的翻译单元使用它时才会有影响,其中一个是使用C++17编译的,而另一个不是。即使如此,由于call是一个模板函数,它可能不会产生太大的影响,除了在最终可执行文件中有一个额外的定义。 - Daniel H
3
@DanielH 我并不是要代表上面的Barry说话,但如果你使用-wError编译一个项目,那么即使这个"无害的警告"实际上是正确的,它也会导致程序根本无法编译。这很重要。 - markt1964
1
@markt1964 在这篇文章中只使用了-Wall。如果你使用-Werror编译或试图避免编译器错误(这是个好主意),那么你会遇到问题。根据情况,也许最好的解决方法是添加-Wno-noexcept-type - Daniel H
@Barry,当您切换编译器版本时,您是否会从头开始重建项目(以及其所有用户)? - Florian Weimer
2
只是一点提醒,但在GCC8中可能会有更少的此警告。不确定它是否适用于您的特定情况。 - Morwenn
4个回答

21

有几种方法可以处理这个警告信息。

使用 -Wno-noexcept-type 命令来禁用它。在许多项目中,警告信息是无用的,因为生成的对象不可能与期望它使用GCC的C++17名称重整的另一个对象链接。如果您没有使用不同的 -std= 设置编译,并且您不建立静态或共享库,其中有问题的函数是其公共接口的一部分,则可以安全地禁用警告信息。

将所有代码编译成 -std=c++17。警告信息将消失,因为该函数将使用新的名称重整。

将函数设为 static。由于该函数不能再由另一个对象文件引用,使用不同的名称重整函数,因此就不会显示警告信息。函数定义必须包含在所有使用它的编译单元中,但对于像示例中的模板函数,这是常见的。但对于成员函数,这种方法不起作用,因为 static 的含义不同。

在调用函数模板时,显式指定模板参数,给一个兼容的函数指针类型,该类型没有异常说明。例如:call<void (*)()>(func)。您也可以使用强制转换来做到这一点,但是 GCC 7.2.0 仍然会生成一个警告,即使使用 -std=c++17 也不会改变名称重整。

当函数不是模板时,请不要在函数类型中使用任何带有异常说明的函数指针类型。这与上一点依赖于仅非抛出函数指针类型会导致名称重整更改,并且可以将非抛出函数指针(C++11)分配或隐式转换(C++17)为可能抛出的函数指针。


2
我正在为Ross的答案点赞,因为call<void (*)()>(func)解决方案明确告诉编译器你想要非noexcept函数类型的模板实例化,并保证你的代码在C++17中的操作与C++14完全相同。
更多的选择是:
(1)将noexcept函数包装在lambda中(这不是noexcept):
template <class Func>
void call(Func f)
{
    f();
}

void func() noexcept { }

int main()
{
    call([]() { func(); });
}

(2) 创建一个不带 noexcept 的单独包装函数。这会增加一些初始的输入量,但如果有多个调用站点,总体来说可以节省输入量。这就是我最初提交 GCC 缺陷报告时在代码中所做的事情

1
除了已经提到的内容,我发现在GCC 7中还有另一种方法可以消除此警告。显然,只有当call()的第一个实例涉及noexcept时,GCC才会生成此警告。因此,解决方案是首先使用非noexcept函数实例化call()
这个技巧也可以起到作用:
using dummy = decltype(call(std::declval<void(*)()>()));

顺便提一句,GCC 8.2.1在这种情况下没有报警告。


0

他们警告你的问题是,在C++14中,这将起作用:

void call(void (*f)())
{
    f();
}

void func() noexcept {}

int main(int argc, char* argv[])
{
    call(&func);
    return 0;
}

但在C++17中,您需要更改call的声明为:

void call(void (*f)() noexcept)
{
    f();
}

由于您已将call定义为模板,因此您不需要担心这个问题。但是,它可能会导致问题,因为推断的类型正在改变,这通常不会发生。

例如,这段代码在C++14中可以编译,但在C++17中无法编译:

void foo() noexcept {}
void bar()          {}

template <typename F>
void call(bool b, F f1, F f2)
{
    if (b)
        f1();
    else
        f2();
}

void foobar(bool b)
{
    call(b, &foo, &bar);
}

在C++14中,foobar的类型相同,但在C++17中它们是不同的,这意味着模板解析将失败。使用标志-std=c++1z的gcc 7.2中的错误消息为:
note:   template argument deduction/substitution failed:
note:   deduced conflicting types for parameter 'F' ('void (*)() noexcept' and 'void (*)()')

在您所提供的例子中,没有问题,您不会在C++14或C++17模式下编译出现问题。 如果代码比这里的示例复杂(例如类似于我上面提供的示例),您可能会遇到一些编译器问题。 看起来您有一个最近的编译器;尝试使用-std=c++1z进行编译,看看是否有警告或错误。

1
这并没有回答问题。我知道将 noexcept 添加到类型系统中。 - Barry
我在结尾添加了更多的解释,希望能够澄清这个问题。 - SJL

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