Template deduction/overload resolution favors T&&

2019-08-04 19:16发布

问题:

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?

回答1:

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));
}


回答2:

The second overload is always an exact match. So there is in fact no need for the first overload and it can only cause ambiguity. Instead of moving, you should forward the argument.

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

Scott Meyers has given an excellent explanation of this: http://scottmeyers.blogspot.nl/2012/11/universal-references-in-c11-now-online.html