Search code examples
c++c++14sfinaeoverload-resolutionusing-declaration

Overload resolution between template members in base and derived classes


Microsoft compiler (Visual Studio 2017 15.2) rejects the following code:

#include <type_traits>

struct B
{ 
    template<int n, std::enable_if_t<n == 0, int> = 0>
    void f() { }
};

struct D : B
{
    using B::f; 
    template<int n, std::enable_if_t<n == 1, int> = 0>
    void f() { }
};

int main()
{
    D d;
    d.f<0>();
    d.f<1>();
}

The error is:

error C2672: 'D::f': no matching overloaded function found
error C2783: 'void D::f(void)': could not deduce template argument for '__formal'
note: see declaration of 'D::f'

Clang also rejects it:

error: no matching member function for call to 'f'
    d.f<0>();
    ~~^~~~
 note: candidate template ignored: disabled by 'enable_if' [with n = 0]
    using enable_if_t = typename enable_if<_Cond, _Tp>::type;

GCC perfectly accepts it. Which compiler is right?

Addition:

With SFINAE in the form

template<int n, typename = std::enable_if_t<n == 0>>
...
template<int n, typename = std::enable_if_t<n == 1>>

GCC also produces an error:

error: no matching function for call to ‘D::f<0>()’
 d.f<0>();
        ^
note: candidate: template<int n, class> void D::f()
 void f()
      ^
note:   template argument deduction/substitution failed:

Solution

  • Turning cppleaner's comment into an answer:

    From namespace.udecl#15.sentence-1:

    When a using-declarator brings declarations from a base class into a derived class, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list, cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting)

    Unfortunately, template parameter doesn't count and both f has empty parameter-type-list, are not const and no ref-qualifier.

    Derived::f so hides Base::f.

    gcc is wrong to accept that code.

    So the way to fix it is by default argument (returned type doesn't count either):

    struct B
    { 
        template <int n>
        void f(std::enable_if_t<n == 0>* = nullptr) { }
    };
    
    struct D : B
    {
        using B::f; 
        template <int n>
        void f(std::enable_if_t<n == 1>* = nullptr) { }
    };