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

Evaluating noexcept specifier before template type deduction

请看下面代码:

#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};
}

此代码似乎被 GCC/Clang 接受,但 MSVC 2017 拒绝了它。 似乎 MSVC 编译器在理解 #1 不是适当的重载(由于 tagint 之间的不兼容)之前尝试计算 noexcept 说明符,因此应该被丢弃。因此,它尝试评估 is_noexcept<int>::value 并发现 noexcept(A{std::declval<int>()}) 格式错误。由于这不是在直接上下文中发生的,因此这不是 SFINAE 的用武之地,所以出现严重错误。

(事实上,我对此不太确定,但我已经确认,如果我在 /*Here*/ 处放置 noexcept(A{std::declval<Args>()...}) 而不是 is_noexcept<Args...>::value 来使内部失败在直接上下文中,MSVC 编译器愉快地丢弃 #1 并调用 #2。这样对吗?)

我怀疑GCC/Clang是对的,MSVC是错的,到底哪个是对的?

这是一个 MSVC 错误。 CWG 1330, in [except.spec]/13 的结果是:

An exception specification is considered to be needed when:

  • in an expression, the function is the unique lookup result or the selected member of a set of overloaded functions ([basic.lookup], [over.match], [over.over]);
  • the function is odr-used or, if it appears in an unevaluated operand, would be odr-used if the expression were potentially-evaluated;
  • the exception specification is compared to that of another declaration (e.g., an explicit specialization or an overriding virtual function);
  • the function is defined; or
  • the exception specification is needed for a defaulted special member function that calls the function. [ Note: A defaulted declaration does not require the exception specification of a base member function to be evaluated until the implicit exception specification of the derived function is needed, but an explicit noexcept-specifier needs the implicit exception specification to compare against. — end note ]

The exception specification of a defaulted special member function is evaluated as described above only when needed; similarly, the noexcept-specifier of a specialization of a function template or member function of a class template is instantiated only when needed.

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

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