处理 gcc 的 noexcept 类型警告

Handling gcc's noexcept-type warning

考虑这个例子,来自 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)
      ^~~~

我应该如何处理这个警告?解决方法是什么?

他们警告您的问题是,在 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中不同,模板解析会失败。 gcc 7.2 中带有标志 -std=c++1z 的错误消息是:

note:   template argument deduction/substitution failed:
note:   deduced conflicting types for parameter 'F' ('void (*)() noexcept' and 'void (*)()')

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

对于警告消息,您可以采取多种措施。

-Wno-noexcept-type 禁用它。在许多项目中,警告消息没有帮助,因为生成的对象不可能与另一个期望它使用 GCC 的 C++17 名称修饰的对象链接。如果您没有使用不同的 -std= 设置进行编译,并且您没有构建静态或共享库,其中有问题的函数是其 public 接口的一部分,那么可以安全地禁用警告消息。

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

创建函数static。由于该函数不能再被另一个目标文件引用,该文件对该函数使用不同的修饰,因此不会显示警告消息。函数定义必须包含在所有使用它的编译单元中,但是对于像您的示例中这样的模板函数,这无论如何都是常见的。这也不适用于成员函数,因为 static 有其他含义。

调用函数模板时,指定模板参数,明确给出没有异常规范的兼容函数指针类型。例如 call<void (*)()>(func)。您也应该能够使用 cast 来执行此操作,但是 GCC 7.2.0 仍然会生成警告,即使使用 -std=c++17 不会更改 mangling。

当函数不是模板时,不要将 noexcept 与函数类型中使用的任何函数指针类型一起使用。这一点和最后一点依赖于这样一个事实,即只有非抛出函数指针类型会导致命名重整更改,并且可以将非抛出函数指针分配 (C++11) 或隐式转换 (C++17) 为可能抛出函数指针。

我赞成罗斯对 call<void (*)()>(func) 解决方案的回答。它明确地告诉编译器您希望为非 noexcept 函数类型实例化模板,并保证您的代码在 C++17 中的操作与在 C++14 中的操作完全相同。

更多选择是:

(1) 在 lambda 中包装 noexcept 函数(不是 noexcept):

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

void func() noexcept { }

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

(2) 创建一个没有noexcept的单独包装函数。这最初需要更多的输入,但如果您有多个呼叫站点,它可以节省整体输入。这是最初提示我提交 GCC 错误的代码中的 what I ended up doing

除了已经说过的之外,我找到了另一种方法来消除 GCC 7 中的这个警告。显然,当且仅当 call() 的第一个实例化涉及 noexcept。因此,解决方案首先是使用非 noexcept 函数实例化 call()

这个技巧也可以:

using dummy = decltype(call(std::declval<void(*)()>()));

P.S。 GCC 8.2.1 在这种情况下不会报告警告。