Search code examples
c++templatesuniversal-reference

Template deduction/overload resolution favors T&& over const T&


I have a pair of function templates defined like so:

template<typename CollectionType>
Foo<CollectionType> f(const CollectionType& v)
{
   return Foo<CollectionType>(v); // copies v into a member variable
}

template<typename CollectionType>
Foo<CollectionType> f(CollectionType&& v)
{
   return Foo<CollectionType>(std::move(v)); // moves v into a member variable
}

If I call f as below:

std::vector<int> v;
f(v);

The VC++ compiler favors the && overload, apparently because it is less specialized. I would like the const& overload to be called in this case--the && version is intended for constructions like f(ReturnAVector()). Is there a way to achieve this without manually specifying the template argument?

After a fair amount of effort, I came up with this:

template<typename CollectionType>
Foo<CollectionType> f(const CollectionType& v)
{
    return Foo<CollectionType>(v); // copies v into a member variable
}

template<typename CollectionType>
typename std::enable_if<std::is_rvalue_reference<CollectionType&&>::value,
    Foo<typename std::remove_reference<CollectionType>::type>>::type
f(CollectionType&& v)
{
    return Foo<CollectionType>(std::move(v)); // moves v into a member variable
}

But wow; is that really the simplest way to get what I'm after?


Solution

  • With:

    std::vector<int> v;
    f(v);
    

    you call f(std::vector<int>&) so

    template<typename CollectionType>
    Foo<CollectionType> f(CollectionType&& v)
    

    is an exact match (universal reference) CollectionType is std::vector<int>& whereas

    template<typename CollectionType>
    Foo<CollectionType> f(const CollectionType& v)
    

    requires a const promotion.

    A possible solution is to add a version non const:

    template<typename CollectionType>
    Foo<CollectionType> f(CollectionType& v)
    

    or to forward your argument, something like:

    template<typename CollectionType>
    Foo<typename std::remove_reference<CollectionType>::type>
    f(CollectionType&& v)
    {
        return Foo<typename std::remove_reference<CollectionType>::type>(std::forward<CollectionType>(v));
    }