C++14 中 noexcept 说明符的奇怪行为
Strange behavior of noexcept specifier in C++14
我发现 C++14 中 noexcept
运算符的一个奇怪行为。
以下代码在 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;
}
执行结果程序,但是我得到了不同的结果,具体取决于编译器:
$ g++ --std=c++14 test.cpp
$ ./a.out
true
false
$ clang++ --std=c++14 test.cpp
$ ./a.out
false
false
根据标准,我不确定哪个是正确的。
更奇怪的是,如果我将上面代码中的第 5 行更改为 #if 0
,那么 gcc 会将代码编译成另一个不同的程序:
$ ./a.out
true
true
如您所见,第二个值已更改。
但是,它仅取决于宏未触及的 thrower
函数的 noexcept
规范。
对此有什么合理的解释,还是只是一个错误?
编辑
结果是在 Ubuntu 18.04(64 位)包存储库中使用 GCC 7.4.0 和 clang 6.0.0 获得的。
我只能在版本 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 {};
这突然 yields two false
s: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
为什么 TESTREF
的定义会影响 is_noexcept_callable
?
你问题的第二部分比较微妙。在这里,问题是 is_noexcept_callable
在你在 main
中使用它之前已经用相关类型 int(int) [noexcept]
实例化了,但是它没有附加,所以 is_noexcept_callable<int(int), int>::value
的结果固定为真。
decltype(id)
是 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 指令的第一个分支时,is_noexcept_callable<int(int),int>::value == true
在 noexcept(evalInt(1,id))
处理后保持不变,因为 id
是 noexcept 并且它会沿着实例化链传播。
因此,下面打印出两个错误:
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;
}
我发现 C++14 中 noexcept
运算符的一个奇怪行为。
以下代码在 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;
}
执行结果程序,但是我得到了不同的结果,具体取决于编译器:
$ g++ --std=c++14 test.cpp
$ ./a.out
true
false
$ clang++ --std=c++14 test.cpp
$ ./a.out
false
false
根据标准,我不确定哪个是正确的。
更奇怪的是,如果我将上面代码中的第 5 行更改为 #if 0
,那么 gcc 会将代码编译成另一个不同的程序:
$ ./a.out
true
true
如您所见,第二个值已更改。
但是,它仅取决于宏未触及的 thrower
函数的 noexcept
规范。
对此有什么合理的解释,还是只是一个错误?
编辑
结果是在 Ubuntu 18.04(64 位)包存储库中使用 GCC 7.4.0 和 clang 6.0.0 获得的。
我只能在版本 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 {};
这突然 yields two false
s: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
为什么 TESTREF
的定义会影响 is_noexcept_callable
?
你问题的第二部分比较微妙。在这里,问题是 is_noexcept_callable
在你在 main
中使用它之前已经用相关类型 int(int) [noexcept]
实例化了,但是它没有附加,所以 is_noexcept_callable<int(int), int>::value
的结果固定为真。
decltype(id)
是 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 指令的第一个分支时,is_noexcept_callable<int(int),int>::value == true
在 noexcept(evalInt(1,id))
处理后保持不变,因为 id
是 noexcept 并且它会沿着实例化链传播。
因此,下面打印出两个错误:
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;
}