Search code examples
c++templatestemplate-specializationenable-if

using enable_if to specialize on return type from a base class


I'm porting a large code base to clang (builds with g++ and intel c++). Code similar to the following snippet compiles and works on g++ 4.8 - 6.2 but fails to compile with clang 3.8 and 3.9. The second invocation of the MinOp should (AFAICT) get the base class specialization ("Don't call me!"), but clang attempts to instantiate the std::min version and fails:

#include <algorithm>
#include <iostream>
#include <type_traits>

template <typename T> class Vector
{
public:
    Vector() : xyz{} {}
    Vector(T const &x, T const &y, T const &z) : xyz{y, y, z} {}
    Vector<T> min(Vector<T> const &v) { return Vector<T>(std::min(xyz[0], v.xyz[0]), std::min(xyz[1], v.xyz[1]), std::min(xyz[2], v.xyz[2])); }
    T xyz[3];
};

class MinOpBase
{
public:
    template <class T> typename std::enable_if<!std::is_fundamental<T>::value>::type
    operator()(Vector<T> &left, Vector<T> const &right) const { std::cout << "Don't call me!" << std::endl; }
};

class MinOp : public MinOpBase
{
public:
    template <typename T> void operator()(T &left, T const &right) const
    { left = std::min(left, right); }

    // Support component-wise min on vectors of fundamental types
    template <typename T> typename std::enable_if<std::is_fundamental<T>::value>::type
    operator() (Vector<T>  &left, Vector<T>  const &right) const
    {  left.min(right); }

    using MinOpBase::operator();
};

int main()
{
    Vector<double> v1, v2;
    Vector<Vector<double>> vv1, vv2;
    MinOp m;
    m(v1,v2);
    m(vv1,vv2);
}

Note that if there is no base class (MinOpBase), and the "Don't call me!" specialization is directly in the MinOp class, clang works too.

Is this use of a using statement to bring in the specialization from the base class valid?

This looks to me like a whole bunch of machinery for not very much value (although of course this has been simplified almost to the point of pointlessness). Better ideas?


Solution

  • A colleague pointed out that my question is a duplicate of SFINAE not working on llvm/clang

    I think the conclusion that clang is doing the "right" thing according to the standard is correct. From Johannes Schaub - litb's answer:

    7.3.3 p15 of C++11 (the using declaration of the inherited function template is ignored because it has the same name and parameters as a member function template of the derived class)

    Although the other compilers are doing what probably ought to be the "right" thing. I don't know if this has been reported as a defect in the standard.

    Work-arounds include putting all specializations into base classes or modifying one of the argument types of one of the specializations.