Reputation: 42379
#include <vector>
template
<
typename T,
typename Alloc,
template<typename, typename> class Left
>
Left<T, Alloc>&&
operator <<(Left<T, Alloc>&& coll, T&& value)
{
coll.push_back(std::forward<T>(value));
return std::forward<Left<T, Alloc>>(coll);
}
using namespace std;
int main()
{
vector<int> c1;
c1 << int(8);
}
VS 2015 outputs:
error C2678 : binary '<<' : no operator found which takes a left - hand operand of type 'std::vector>' (or there is no acceptable conversion)
Why does template template parameters not work as expected?
Upvotes: 0
Views: 152
Reputation: 62995
Your function takes an rvalue reference but you're passing an lvalue – Left<T, Alloc>&&
is not a forwarding reference, so treating it as such with std::forward
etc. is not correct. For now we'll disallow collection rvalues to simplify things:
template<
typename T,
typename Alloc,
template<typename, typename> class Left
>
Left<T, Alloc>& operator <<(Left<T, Alloc>& coll, T&& value) {
coll.push_back(std::forward<T>(value));
return coll;
}
The above is one step closer, but will not work if passed an lvalue for value
. One option is to force the correct argument for Left
:
template<
typename T,
typename Alloc,
template<typename, typename> class Left
>
Left<typename std::decay<T>::type, Alloc>&
operator <<(Left<typename std::decay<T>::type, Alloc>& coll, T&& value) {
coll.push_back(std::forward<T>(value));
return coll;
}
This works, but doesn't leave us any easy way to support collection rvalues. The correct solution here IMO is to stop using template-templates and either static_assert
that the container's value_type
matches T
or SFINAE the operator away if it doesn't:
template<typename Coll, typename T>
Coll& operator <<(Coll& coll, T&& value) {
static_assert(std::is_same<
typename std::decay<T>::type,
typename Coll::value_type
>::value,
"T does not match Coll::value_type"
);
coll.push_back(std::forward<T>(value));
return coll;
}
or
template<typename Coll, typename T>
typename std::enable_if<std::is_same<
typename std::decay<T>::type,
typename Coll::value_type
>::value,
Coll&
>::type
operator <<(Coll& coll, T&& value) {
coll.push_back(std::forward<T>(value));
return coll;
}
With this done, now if you decide that you want to support collection rvalues after all, this is trivially done; to use the static_assert
implementation as an example:
template<typename Coll, typename T>
Coll&& operator <<(Coll&& coll, T&& value) {
static_assert(std::is_same<
typename std::decay<T>::type,
typename std::decay<Coll>::type::value_type
>::value,
"T does not match Coll::value_type"
);
coll.push_back(std::forward<T>(value));
return std::forward<Coll>(coll);
}
N.b. the above implementation only allows use of the operator with exact matches to Coll::value_type
, but it is probably sensible to allow anything that can be converted to Coll::value_type
– to implement this, just replace std::is_same
with std::is_convertible
.
Upvotes: 6