C++14 中 noexcept 说明符的奇怪行为

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)

如您所见,第二个值已更改。但是,它仅取决于宏不涉及noexceptthrower功能规范。对此有什么合理的解释,还是只是一个错误?


编辑

The result is obtained with GCC 7.4.0 and clang 6.0.0 in Ubuntu 18.04 (64bit) package repository.

Col*_*mbo 4

我只能在版本 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