jun*_*ogy 7 c++ language-lawyer noexcept c++14
我noexcept在 C++14 中发现了一个奇怪的操作符行为。以下代码可以通过 gcc 和 clang(使用 --std=c++14 选项)很好地编译。
// test.cpp
#include <iostream>
#include <type_traits>
#if 1
#define TESTREF(X) X&&
#else
#define TESTREF(X) X const&
#endif
template <class F, class... Args>
struct is_noexcept_callable
: public std::conditional_t<noexcept(std::declval<F>()(std::declval<Args>()...)), std::true_type, std::false_type> {};
template <
class F,
std::enable_if_t<is_noexcept_callable<F,int>::value,int> = 0
>
int evalInt(int x, TESTREF(F) f) noexcept
{
return static_cast<int>(f(x));
}
template <
class F,
std::enable_if_t<!is_noexcept_callable<F,int>::value,int> = 0
>
int evalInt(int x, TESTREF(F) f)
{
return static_cast<int>(f(x));
}
int id(int x) noexcept { return x; }
int thrower(int x) { throw(0); }
int main(int argc, char* argv[])
{
std::cout << std::boolalpha
<< noexcept(evalInt(1,id))
<< std::endl;
std::cout << std::boolalpha
<< is_noexcept_callable<decltype(thrower), int>::value
<< std::endl;
}
Run Code Online (Sandbox Code Playgroud)
执行结果程序,但是根据编译器的不同,我得到了不同的结果:
$ g++ --std=c++14 test.cpp
$ ./a.out
true
false
$ clang++ --std=c++14 test.cpp
$ ./a.out
false
false
Run Code Online (Sandbox Code Playgroud)
我不确定根据标准哪个是正确的。
更奇怪的是,如果我将上面代码中的第 5 行更改为#if 0gcc 会将代码编译为另一个不同的程序:
$ ./a.out
true
true
Run Code Online (Sandbox Code Playgroud)
如您所见,第二个值已更改。但是,它仅取决于宏不涉及noexcept的thrower功能规范。对此有什么合理的解释,还是只是一个错误?
编辑
The result is obtained with GCC 7.4.0 and clang 6.0.0 in Ubuntu 18.04 (64bit) package repository.
我只能在版本 8 之前的 GCC 中重现此错误。行为差异是由于noexcept说明符是 GCC 7 的 C++14 版本(但不是 Clang 的)中函数类型的一部分,尽管这是 C++17 功能。如果我们添加以下部分特化,就可以看出这一点is_noexcept_callable:
template <class... Args>
struct is_noexcept_callable<int(&)(int), Args...>
: public std::false_type {};
template <class... Args>
struct is_noexcept_callable<int(int), Args...>
: public std::false_type {};
Run Code Online (Sandbox Code Playgroud)
这突然产生了两个falses:GCC 保留noexcept函数类型上的属性,但在模板参数推导期间显式忽略它们,以便选择上述专业化,尽管错误消息显示noexcept我们是否删除定义:
prog.cc:30:5: note: template argument deduction/substitution failed:
prog.cc:28:22: error: incomplete type 'is_noexcept_callable<int (&)(int) noexcept, int>' used in nested name specifier
Run Code Online (Sandbox Code Playgroud)
TESTREF定义会产生影响is_noexcept_callable?你问题的第二部分更加微妙。这里的问题是,在您在 中使用它之前,is_noexcept_callable它已经用相关类型实例化了,但它附加了 noexcept,因此 的结果被固定为 true。int(int) [noexcept]mainis_noexcept_callable<int(int), int>::value
decltype(id)is int(int) [noexcept],其中[noexcept]是我的符号,用于表达附加到函数类型的 GCC 瞬态异常规范。因此evalInt(1,id)导致实例化
is_noexcept_callable<F,int>F = int(&)(int) [noexcept]何时何TESTREF = X&&地F = int(int) [noexcept]什么时候TESTREF = X const&因此,当您禁用 if 指令的第一个分支时, then会在处理is_noexcept_callable<int(int),int>::value == true之后保留 ,因为is noexcept 并且这会沿着实例化链传播。noexcept(evalInt(1,id))id
因此,以下打印两个错误:
int main(int argc, char* argv[])
{
std::cout << std::boolalpha
<< noexcept(evalInt(1,thrower))
<< std::endl;
std::cout << std::boolalpha
<< is_noexcept_callable<decltype(thrower), int>::value
<< std::endl;
}
Run Code Online (Sandbox Code Playgroud)
演示: https: //wandbox.org/permlink/YXDYfXwtEwMQkryD