1

I was originally trying to write a wrapper class for std::vector. I had a templated constructor, that forwards a parameter pack to imitate different constructors for std::vector. Then I decided to had some specialized/overloaded ones if a std::vector was passed in it.

While writing it, I decided to try how different function overload and template specialization works with.

Here are the codes that I have:

template<typename Type>
struct myVec
{
    template <typename ...Params>
    myVec(Params&&... params)
    :vec(std::forward<Params>(params)...)
    {
        std::cout << "Generic used\n";
    }
    
    template <>
    myVec<std::vector<Type>&&>(std::vector<Type>&& params)
    {
        std::cout << "Specialization used\n";
    }
    
    myVec(std::vector<Type>& params)
    {
        std::cout << "Overload used\n";
    }
    
private:
    std::vector<Type> vec;
};

int main()
{
    std::vector<int> v{1,2,3,4,5};
    myVec<int> vec1(v);                                 // Print "Overload Used"
    myVec<int> vec2(std::vector<int> {1,2,3,4,5});      // Print "Specialization used"
    // myVec<int> vec3(&v);                             // Error, attempting to use generic template, 
                                                        // which attempt to search for std::vector(std::vector& vec), which does not exist
}

So my question would how exactly do they decide which constructors to use? I thought vec1 would use the generic template, and vec3 would use the the overload, but apparently those are not the cases.

Also seems like for my specialization, I could just replace the explicit template <std::vector<Type>&&>to <std::vector<Type>> to <>, or just remove it all together, and they would all work the same for me?

Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39
  • For `vec1` I think __4)__ in section __Best viable function__ https://en.cppreference.com/w/cpp/language/overload_resolution The whole Overload resolution is complicated and I don't follow 90% of the linked page. – Richard Critten Apr 21 '21 at 00:29
  • FWIW, this is why function specializations is being removed from the standard library. They don't work how you would normally expect them to work. Everything you can do with specialization, you can do with overloading, and overloading is easier to deal with. – NathanOliver Apr 21 '21 at 00:37
  • Also, why do you expect `v3` to use the overload? `&v` is `std::vector*`, not `std::vector&` – NathanOliver Apr 21 '21 at 00:40
  • 2
    In overload resolution, a non-template function is preferred over a template, other things equal. That's why `myVec(std::vector& params)` is chosen for `vec1`. For `vec2`, that overload is not viable, since a non-const lvalue reference can't bind to a temporary. For `vec3`, the primary template is chosen but is ill-formed, since it tries to construct `std::vector` from `std::vector*` argument. – Igor Tandetnik Apr 21 '21 at 01:55
  • You can’t specialize a constructor template with a template argument list like that—how does this even compile? – Davis Herring Apr 21 '21 at 02:16

1 Answers1

1

Firstly, your code does not compile on GCC (I'm guessing you're using VS?) This is because you can't specialize the template for myVec constructor without specializing the whole class. See this: Explicit specialization in non-namespace scope

As you said in the last paragraph of the question, there is no need of the specialization anyways, so you can just remove it. This code works:

#include <iostream>
#include <vector>

template<typename Type>
struct myVec
{
    template <typename ...Params>
    myVec(Params&&... params)
    :vec(std::forward<Params>(params)...)
    {
        std::cout << "Generic used\n";
    }
    
    myVec(std::vector<Type>&& params)
    {
        std::cout << "Specialization used\n";
    }
    
    myVec(std::vector<Type>& params)
    {
        std::cout << "Overload used\n";
    }
    
private:
    std::vector<Type> vec;
};

int main()
{
    std::vector<int> v{1,2,3,4,5};
    myVec<int> vec1(v);                              // Print "Overload Used"
    myVec<int> vec2(std::vector<int> {1,2,3,4,5});    // Print "Specialization used"
    // myVec<int> vec3(&v);                          // Error, attempting to use generic template, 
                                                        // which attempt to search for std::vector(std::vector& vec), which does not exist
}

vec1 uses the overloaded template because, well.... it is a direct match. No other constructors match the argument v. This is because when you pass v to the constructor, it will be passed as an lvalue reference, that is std::vector<int>& (or const vector<int>&). See lvalue references:

https://en.cppreference.com/w/cpp/language/reference

vec2 uses the 'specialized' template because it expects an rvalue reference and std::vector<int>{1, 2, 3, 4, 5} will be passed as an rvalue reference. So it's an exact match.

Hernando Abella
  • 286
  • 2
  • 13