与SFINAE中的硬错误混淆

时间:2017-08-09 07:48:40

标签: c++ templates c++17 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上下文中不可用。但奇怪的是,当你在void_t EnableIfHasFoo中切换事物的顺序(到下面的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 个答案:

答案 0 :(得分:7)

1)和2)有相同的答案; SFINAE does not work return type deduction not in immediate context,因为函数的主体是a more interesting question

  

10 - 函数模板的返回类型推导在其声明的类型中具有占位符   即使函数体包含具有非类型相关的return语句,也会实例化定义   操作数。 [注意:因此,任何使用函数模板的特化都会导致隐式   实例。此实例化产生的任何错误都不在函数的直接上下文中   输入并导致程序格式不正确(17.8.2)。 - 结束记录]

3)是[temp.deduct];短路是故意的,由for example保证:

  

7 - [...]替换进行   以词汇顺序,并在遇到导致演绎失败的条件时停止。

这种短路适用于gcc,clang和ICC,但遗憾的是MSVC(截至2017年第9版RTW)出错了,System.Web.Helpers.Crypto.VerifyHashedPassword

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);