处理 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中,foo
和bar
的类型相同,但在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 在这种情况下不会报告警告。
考虑这个例子,来自 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中,foo
和bar
的类型相同,但在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 在这种情况下不会报告警告。