luxderfux
luxderfux

Reputation: 173

C++ Function templates specialization for templated base class

I am trying to implement a member function template specialization for a templated class and all derived children. It works for the class itself but not the children.

Please have a look at following example demonstrating the problem:

#include <vector>

struct MyVector : public std::vector<int> {};

struct S {
    template <typename T>
    int make(T value) {
        return 1;
    }

    template <typename T>
    int make(const std::vector<T>& value) {
        return 2;
    }
};

int main() {
    S s;
    // return s.make(std::vector<int>{}); // returns 2, perfekt!
    return s.make(MyVector{}); // returns 1 but should return 2
}

Live: https://godbolt.org/z/PgYkKf

I searched on stackoverflow for hours but couldn't find a proper solution.. I'm grateful for any tips!

Upvotes: 2

Views: 136

Answers (1)

function template specialization

First order of business. This is not a specialization. It's important to get this out of the way to get at a solution. This is overloading. The two function templates overload the make name. And overload resolution determines which is a better match after synthesizing their signatures. In your case, the two signatures the compiler can deduce are

int make(MyVector);
int make(std::vector<int> const&);

One is the identity conversion, while the other requires binding a reference to a base, which ranks worse than "identity" as far as conversions go. So the first overload gets chosen.

You have a few options before you:

  1. Prefer a type alias like using MyVector = std::vector<int>;. It's streight forward, matches the second overload exactly, and will make it get chosen due to partial ordering of function templates.

  2. Add a MyVector overload, that delegates:

    int make(MyVector const& v) { return make(static_cast<MyVector::vector const&>(v)); }
    
  3. Use a more sophisticated technique to control overload resolution. The standard library has a few utilites for SFINAE based control of the process. It would require changing the first overload to this:

    template <typename T>
    std::enable_if_t<!std::is_convertible_v<T*, std::vector<int>*>, int>
    make(T value) {
        return 1;
    }
    

    But this approach is overly expert friendly and will quickly not scale with many template overloads. I recommend the previous two approaches first.

Upvotes: 3

Related Questions