Template deduction/overload resolution favors T&&

2019-08-04 18:55发布

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?

2条回答
爷、活的狠高调
2楼-- · 2019-08-04 19:41

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));
}
查看更多
神经病院院长
3楼-- · 2019-08-04 19:41

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

查看更多
登录 后发表回答