具有 decltype 的 C++ SFINAE:替换失败变成错误?

C++ SFINAE with decltype: substitution failure becomes an error?

此代码有效:

// Code A
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;

template <typename T>
struct S {
    template <typename Iter, typename = typename enable_if<is_constructible<T, decltype(*(declval<Iter>()))>::value>::type>
    S(Iter) { cout << "S(Iter)" << endl; }

    S(int) { cout << "S(int)" << endl; }
};

int main()
{
    vector<int> v;
    S<int> s1(v.begin()); // stdout: S(Iter)
    S<int> s2(1);         // stdout: S(int)
}

但是下面这段代码不起作用。在下面的代码中,我只想继承 std::enable_if,因此如果 std::enable_if 的选定版本具有成员 typedef,则 class is_iter_of 将具有成员 typedef type type.

// Code B
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;

template <typename Iter, typename Target>
struct is_iter_of : public enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value> {}

template <typename T>
struct S {
    template <typename Iter, typename = typename is_iter_of<Iter, T>::type>
    S(Iter) { cout << "S(Iter)" << endl; }

    S(int) { cout << "S(int)" << endl; }
};

int main()
{
    vector<int> v;
    S<int> s1(v.begin());
    S<int> s2(1);   // this is line 22, error
}

错误信息:

In instantiation of 'struct is_iter_of<int, int>':
12:30:   required by substitution of 'template<class Iter, class>     S<T>::S(Iter) [with Iter = int; <template-parameter-1-2> = <missing>]'
22:16:   required from here
8:72: error: invalid type argument of unary '*' (have 'int')

错误信息令人困惑:当然我希望模板替换失败..所以可以选择正确的构造函数。为什么 SFINAE 没有在 Code B 工作?如果 invalid type argument of unary '*' (have 'int') 冒犯了编译器,编译器也应该对 Code A 发出相同的错误。

问题是表达式 *int (*(declval<Iter>())) 无效,因此您的模板失败。您需要另一个级别的模板,所以我建议使用 void_t 方法:

  • 通过派生自true_typefals_type

  • 的精简概念制作is_iter_of
  • 在 class 的定义中使用 enable_if 来启用迭代器构造函数。

要理解的关键是,您的构造函数之前需要 typename is_iter_of<Iter, T>::type 的类型,只是 struct is_iter_of 中的 enable_if 导致整个内容格式错误。由于没有后备模板,您遇到了编译器错误。

template<class...>
using voider = void;

template <typename Iter, typename Target, typename = void>
struct is_iter_of : std::false_type{};

template <typename Iter, typename Target>
struct is_iter_of<Iter, Target, voider<decltype(*(declval<Iter>()))>> : std::is_constructible<Target, decltype(*(declval<Iter>()))> {};

template <typename T>
struct S {
    template <typename Iter, typename std::enable_if<is_iter_of<Iter, T>::value, int>::type = 0>
    S(Iter) { cout << "S(Iter)" << endl; }

    S(int) { cout << "S(int)" << endl; }
};

Demo (C++11)


发生了什么事

如果 *(declval<Iter>()) 是一个格式不正确的表达式 (*int),附加的 voider 使得模板特化不是首选,因此回退基本模板 (std::false_type)被选中。

否则,它将派生自 std::is_constructible``. In other words, it can still derive fromstd::false_typeif the expression is well-formed but it's not constructibe, andtrue_type` 否则。

问题是您正在尝试从 std::enable_if 扩展,但是您放在 enable if 中的表达式可能无效。由于您使用的 class 继承自该形式,因此您实例化的 class 从无效表达式继承,因此出现错误。

enable_if 表达式命名的一个简单解决方案是使用别名而不是 class:

template <typename Iter, typename Target>
using is_iter_of = enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value>;

SFINAE 仍可使用别名按预期工作。

这是因为实例化别名是您尝试应用 SFINAE 的函数的一部分。通过继承,表达式是被实例化的 class 的一部分,而不是函数。这就是您遇到硬错误的原因。

事实是,SFINAE 在您的案例中有多种应用方式。让我们来看看SFINAE可以发生在哪里:

enable_if< //             here -------v
    is_constructible<Target, decltype(*(declval<Iter>()))>::value
>::type
//  ^--- here

确实会发生SFINAE,因为如果bool参数为false,enable_if::type将不存在,导致SFINAE。

但如果仔细观察,另一种类型可能不存在:decltype(*(std::declval<Iter>()))。如果 Iterint,询问星号运算符的类型是没有意义的。所以 SFINAE 如果也在那里应用。

如果您作为 Iter 发送的每个 class 都具有可用的 * 运算符,那么您的继承解决方案将会有效。由于 int 它不存在,因此您正在向 std::is_constructible 发送一个不存在的类型,从而使构成基础 class 的整个表达式无效。

加上别名,整个使用std::enable_if的表达式以应用SFINAE为准。而基础 class 方法只会将 SFINAE 应用于 std::enable_if.

的结果