C ++ SFINAE与decltype:替换失败成为错误?

时间:2017-08-26 03:40:03

标签: c++ sfinae

此代码有效:

// 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,因此如果is_iter_of的所选版本具有成员typedef {{1},则类type将具有成员typedef std::enable_if }。

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
}

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

2 个答案:

答案 0 :(得分:5)

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

  • 通过一个源自is_iter_oftrue_type

  • 的概念精简版fals_type
  • 在类的定义中使用enable_if来启用迭代器构造函数。

要理解的关键是你的构造函数在需要typename is_iter_of<Iter, T>::type的类型之外,enable_if中的struct is_iter_of导致整个事情的格式不正确。由于没有后备模板,因此编译错误。

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)

发生了什么

如果voider是格式错误的表达式(*(declval<Iter>())),则附加*int会使模板专精化不受欢迎,因此选择了后备基本模板(std::false_type

否则,它将从std::is_constructible``. In other words, it can still derive from std :: false_type if the expression is well-formed but it's not constructibe, and true_type`派生而来。

答案 1 :(得分:4)

问题是你试图从std::enable_if扩展,但你在启用中的表达式可能无效。由于您使用的是继承表单的类,因此您实例化的类将从无效表达式继承,因此会出错。

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

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

SFINAE仍然可以按预期使用别名。

这是因为实例化别名是您尝试应用SFINAE的功能的一部分。对于继承,表达式是实例化的类的一部分,而不是函数。这就是你遇到一个严重错误的原因。

问题是,在您的情况下应用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发送的每个班级都有*运算符,那么您的继承解决方案就会有效。由于int不存在,您将向std::is_constructible发送一个非现有类型,使整个表达式构成基类无效。

使用别名,使用std::enable_if的整个表达式适用于SFINAE。而基类方法仅对std::enable_if的结果应用SFINAE。