xmllmx
xmllmx

Reputation: 42379

Why does template template parameters not work as expected?

#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

Answers (1)

ildjarn
ildjarn

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

Online Demo

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

Online Demo

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

Online Demo

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

Online Demo

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

Related Questions