与 SFINAE 中的硬错误混淆

Confusion with hard error in SFINAE

关于以下代码(https://wandbox.org/permlink/nhx4pheijpTF1ohf为了方便转载如下)

#include <type_traits>
#include <utility>

namespace foo_name {
template <typename T>
void foo();
template <>
void foo<int>();

template <typename T>
struct c_size;
template <>
struct c_size<int> : public std::integral_constant<int, 1> {};
} // namespace foo_name

template <typename Type>
class Foo {
public:
    template <typename T>
    static decltype(auto) impl(T&& t) {
        using foo_name::foo;
        return foo(std::forward<T>(t));
    }
};

class Something {};

template <typename Type, typename T = std::decay_t<Type>>
using EnableIfHasFoo = std::void_t<
    decltype(Foo<T>::impl(std::declval<T>())),
    decltype(foo_name::c_size<Type>::value)>;

template <typename Type, typename = std::void_t<>>
class Test {};
template <typename Type>
class Test<Type, EnableIfHasFoo<Type>> {};

int main() {
    static_cast<void>(Test<Something>{});
}

上面的代码退出时出错,因为 Foo<T>::impl() 的实例化导致硬错误并且在 SFINAE 上下文中不可用。但是这里奇怪的是,当你把EnableIfHasFoo里的void_t里的东西顺序调换一下(到后面的https://wandbox.org/permlink/at1KkeCraNwHGmUI),就会编译

template <typename Type, typename T = std::decay_t<Type>>
using EnableIfHasFoo = std::void_t<
    decltype(foo_name::c_size<Type>::value),
    decltype(Foo<T>::impl(std::declval<T>()))>;

现在问题是

  1. 为什么代码最初无法编译? Foo<T>::impl() 的实例化是在替换的上下文中,所以它应该有效?
  2. foo_name::foo(T) 代替 void_t 的第一个参数将使它编译(参见 https://wandbox.org/permlink/g3NaPFZxdUPBS7oj),为什么?添加一个额外的间接层如何使情况有所不同?
  3. 为什么 void_t 中的顺序会有所不同,编译器是否会缩短类型包中的表达式?

1)和2)答案相同; with return type deduction since the body of a function is not in immediate context:

10 - Return type deduction for a function template with a placeholder in its declared type occurs when the definition is instantiated even if the function body contains a return statement with a non-type-dependent operand. [ Note: Therefore, any use of a specialization of the function template will cause an implicit instantiation. Any errors that arise from this instantiation are not in the immediate context of the function type and can result in the program being ill-formed (17.8.2). — end note ]

3) 是 a more interesting question; the short-circuiting is intentional, and is guaranteed by [temp.deduct]:

7 - [...] The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered.

这种短路适用于 gcc、clang 和 ICC,但不幸的是 MSVC(截至 CL 19 2017 RTW)弄错了,for example:

template<class T> auto f(T t) -> decltype(t.spork) { return t.spork; }
template<class T> auto g(T t) { return t.spork; }
int x(...);
template<class...> using V = void;
template<class T> auto x(T t) -> V<decltype(f(t)), decltype(g(t))> {}
int a = x(0);