在模板类型推导之前评估noexcept说明符

Jun*_*eon 9 c++ language-lawyer noexcept c++11

请参阅以下代码:

#include <utility>

struct A {
  A(int, int) {}
};

struct tag {};

template <class... Args>
struct is_noexcept {
  static constexpr bool value = noexcept(A{std::declval<Args>()...});
};

struct B : A {
  //#1
  template <class... Args>
  B(tag, Args&&... args) noexcept(/*Here*/is_noexcept<Args...>::value) :
    A{std::forward<Args>(args)...} {}

  //#2
  B(int x, int y) : A{x, y} {}
};

int main()
{
  B x{0, 0};
}
Run Code Online (Sandbox Code Playgroud)

这段代码似乎被GCC/Clang接受,但MSVC 2017拒绝接受.似乎MSVC编译器试图理解是#1不是适当的过载(由于之间的不相容之前计算noexcept符tagint),因此应被丢弃.因此,它试图评估is_noexcept<int>::value并发现noexcept(A{std::declval<int>()})是不正确的.由于这不是在紧急情况下发生的,因此这不是SFINAE的用武之地,所以很难发生错误.

(其实,我不是很肯定这一点,但我已经证实,如果我把noexcept(A{std::declval<Args>()...}),而不是is_noexcept<Args...>::value/*Here*/做直接的上下文中发生故障时,MSVC编译器愉快地下降#1和#调用2.这是正确的? )

我怀疑GCC/Clang是对的,MSVC是错的,但哪一个是正确的?

Bar*_*rry 5

这是一个MSVC错误.作为CWG 1330的结果,[except.spec]/13中的规则是:

在以下情况下,需要考虑异常规范:

  • 在表达式中,该函数是唯一的查找结果或一组重载函数的选定成员([basic.lookup],[over.match],[over.over]);
  • 该函数是odr-used,或者,如果它出现在未评估的操作数中,如果表达式被潜在评估,则会使用该函数;
  • 将异常规范与另一个声明的规范进行比较(例如,显式特化或覆盖虚函数);
  • 功能定义; 要么
  • 调用该函数的默认特殊成员函数需要异常规范.[注意:默认声明不需要评估基本成员函数的异常规范,直到需要派生函数的隐式异常规范,但显式n​​oexcept-specifier需要隐式异常规范进行比较. - 结束说明]

仅在需要时如上所述评估默认特殊成员函数的异常规范; 类似地,仅在需要时实例化函数模板的特化或类模板的成员函数的noexcept-specifier.

B(tag, Args&&...)不需要异常规范(我们不满足任何这些项目符号),因此不应该实例化它.

另请注意,异常规范中的替换失败不是直接上下文的一部分,并且不会是SFINAE友好的错误.