C ++为什么SFINAE只有一个类模板参数失败?

时间:2015-06-15 21:34:12

标签: c++ templates c++11 sfinae

我正在使用this answer样式的SFINAE,以便使用适当的成员函数调用泛型矢量对象。例如,以下代码首先调用operator[](int) const,如果不存在则operator()(int) const

template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); };
template<> struct rank<0> {};

template<typename VectorType>
struct VectorWrapper
{
    auto get(int i) const
    {
        return get(v, i, rank<5>());
    }

    template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
    auto get(V const& v, int i, rank<2>) const
    {
        return v[i];
    }

    template<typename V, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> >
    auto get(V const& v, int i, rank<1>) const
    {
        return v(i);
    }

    VectorType v;
};

根据this thread中的建议设置has_bracket_operatorhas_parenthesis_operator特征,整个编译和seems to work

但是,从第一个开始,将成员向量传递给重载的类模板似乎是不必要的,所以我尝试设置相同而不传递它。为此,我将模板参数V替换为用于设置类模板的VectorType参数:

    template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
    auto get(int i, rank<2>) const
    {
        return v[i];
    }

    template<typename = std::enable_if_t<has_parenthesis_operator<VectorType>::value> >
    auto get(int i, rank<1>) const
    {
        return v(i);
    }

然而,现在,编译失败(在gcc 5.1.0中),并显示以下错误消息:

/usr/local/include/c++/5.1.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = has_parenthesis_operator<std::vector<int> >::value; _Tp = void]':
main.cpp:46:10:   required from 'struct VectorWrapper<std::vector<int> >'
main.cpp:59:38:   required from here
/usr/local/include/c++/5.1.0/type_traits:2388:61: error: no type named 'type' in 'struct std::enable_if<false, void>'
     using enable_if_t = typename enable_if<_Cond, _Tp>::type;

DEMO

问题:

  • 编译错误的原因是什么?
  • 除了我的第一个代码块之外,是否有适当的解决方法? (也就是说,保留了通常的编码风格 - 一个人不必通过成员)。

3 个答案:

答案 0 :(得分:4)

在失败的示例中,模板参数@cache_disk.wrapper已经由get解决时间确定。要使SFINAE正常工作,您需要在该方法调用时使用于SFINAE的模板参数解析。以下是您想要的第一个示例的修改:

VectorType

这样,template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); }; template<> struct rank<0> {}; template<typename VectorType> struct VectorWrapper { auto get(int i) const { return get(v, i, rank<5>()); } template<typename V=VectorType, typename = std::enable_if_t<has_bracket_operator<const V>::value> > auto get(int i, rank<2>) const { return v[i]; } template<typename V=VectorType, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> > auto get(int i, rank<1>) const { return v(i); } VectorType v; }; 在调用V时解析,并且会正确使用SFINAE。

答案 1 :(得分:3)

SFINAE来自[temp.deduct] / 8,强调我的:

  

如果替换导致无效的类型或表达式,则类型推导失败。无效的类型或表达式   如果使用替代参数编写,则需要诊断的格式错误。 [ 注意:   如果不需要诊断,程序仍然是不正确的。访问检查是替换的一部分   处理。 -end note] 只有函数类型的直接上下文中的无效类型和表达式   其模板参数类型可能导致演绎失败。

直接上下文是模板声明中的内容。在您的初始示例中:

template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(V const& v, int i, rank<2>) const

V处于直接上下文中,因此enable_if上的替换失败只是一个演绎失败。

然而,在你的第二个例子中:

template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const

VectorType不在get的直接上下文中,因此这里的失败不会是演绎失败,这将是一个很难的错误。

除非VectorType碰巧拥有所有这些运营商。

任何模板问题的解决方案是只添加更多模板。在这种情况下,通过引入另一种类型强制VectorType处于直接上下文中:

template<typename T=VectorType, typename = std::enable_if_t<has_bracket_operator<T>::value> >
auto get(int i, rank<2>) const

并致电get<>()

答案 2 :(得分:3)

或者您可以使用标记调度

auto get(int i) const
{
    return get(i, has_bracket_operator<VectorType>(), has_parenthesis_operator<VectorType>());
}

auto get(int i, std::true_type /*brackets*/, std::false_type /*parenthesis*/) const
{
    return v[i];
}

auto get(int i, std::false_type /*brackets*/, std::true_type /*parenthesis*/) const
{
    return v(i);
}

demo