Yola
Yola

Reputation: 19023

SFINAE on template member overloading

I would like to specialize getVector member function, i'm trying to use SFINAE for this. But it works only if Dim is 3 or greater.

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>'

Upvotes: 2

Views: 165

Answers (1)

Massimiliano Janes
Massimiliano Janes

Reputation: 5624

This is going to be tricky.

I believe your code is ok for Dim>=2 but it's ill-formed when given a Dim<=1 argument, with no diagnostic required, but for a different reason your compiler is complaining about.


For Dim>=2 it is correct, but your compiler (MSVC++ I guess) complains in the Dim==2 case. Given the error description, I suppose the reason is that it's erroneously short-circuiting the && expressions in the enable_if conditions, interpreting them as value-dependent only on the class template parameter when Dim > x is false. The workaround is to move the Dim > x checks as the last term of the && expression.

To elaborate, the situation is conceptually similar to the following code snippet:

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

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

here, the instantation of foo triggers the instantiation of the declarations (but not the definitions) of its members (see [temp.inst]#1); but, only checks of names and expressions that depend on the member template parameters are postponed at their respective instantiation points. Here, the type name enable_if_t<(N>0)> does not depend on any bar() template parameter (N belongs to foo's template parameters list) hence is non-dependent, resulting in enable_if<false>::type when N == 0, so the error.


Going back to your code, consider:

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

and nowhere shortcircuiting operators are mentioned as an exception. So, the expression, say, Dim > 1 && VDim == 0 is value-dependent even if Dim<=1; therefore, no error should occur until substitution of VDim (where SFINAE would apply). In fact, both gcc and clang agree on accepting your code.

That said, when Dim<=1 the first and third getVectorBegin overloads actually declare functionally equivalent member templates (see [temp.over.link]#6), so I believe it's ill-formed in that case.

Upvotes: 2

Related Questions