SFINAE 关于模板成员重载

SFINAE on template member overloading

我想专门化 getVector 成员函数,我正尝试为此使用 SFINAE。但它仅在 Dim 为 3 或更大时才有效。

template <size_t Dim>
class Mat
{
    ...
    template <size_t VDim, typename enable_if<(Dim > 1 && VDim == 0)>::type* = nullptr>
        void getVectorBegin(const array<size_t, Dim - 1>& indexAfter) const;

    template <size_t VDim, typename enable_if<(Dim > 2 && 0 < VDim && VDim < Dim-1)>::type* = nullptr>
        void getVectorBegin(const array<size_t, VDim>& indexBefore, const array<size_t, Dim - VDim - 1>& indexAfter) const;

    template <size_t VDim, typename enable_if<(Dim > 1 && VDim == Dim-1)>::type* = nullptr>
        void getVectorBegin(const array<size_t, Dim - 1>& indexBefore) const;
};

Mat<3> m;
Mat<2> m; // error C2039: 'type': is not a member of 'std::enable_if<false,_Ty>'

这会很棘手。

我相信你的代码对于 Dim>=2 是没问题的,但是当给定一个 Dim<=1 参数时它的格式不正确,不需要诊断,但是你的编译器抱怨的原因不同。


对于 Dim>=2 它是正确的,但是您的编译器(我猜是 MSVC++)在 Dim==2 的情况下会抱怨。鉴于错误描述,我想原因是它错误地短路了 enable_if 条件中的 && 表达式,将它们解释为值依赖 only only class 模板参数,当 Dim > x 为假时。解决方法是将 Dim > x 检查移动到 && 表达式的最后一项。

具体来说,这种情况在概念上类似于以下代码片段:

template <size_t N>
class foo
{
    template <typename E = enable_if_t<(N>0)>>
    void bar();
};

foo<1> f1;
foo<0> f0; // fails

此处,foo 的实例化触发了其成员的声明(但不是定义)的实例化(参见 [temp.inst]#1);但是,只有在 member 模板参数上 depend 的名称和表达式的检查才会在它们各自的实例化点被推迟。这里,类型名称 enable_if_t<(N>0)> 依赖于任何 bar() 模板参数(N 属于 foo 的模板参数列表) 因此是非依赖的,导致 enable_if<false>::typeN == 0,所以错误。


回到您的代码,考虑:

[temp.dep.constexpr]#1 Except as described below, a constant expression is value-dependent if any subexpression is value-dependent

并且没有任何地方提到短路运算符作为例外。因此,即使 Dim<=1,表达式 Dim > 1 && VDim == 0 也是依赖于值的;因此,在替换 VDim(适用 SFINAE 的地方)之前,不会出现错误。事实上,gcc 和 clang agree 都在接受你的代码。

也就是说,当 Dim<=1 第一个和第三个 getVectorBegin 重载实际上声明 功能等效 成员模板(参见 [temp.over.link]#6), 所以我认为在那种情况下它的格式不正确。