Вопрос

I implemented a C++ equivalent of Python's chain function a while ago thanks to variadic templates. The function is used to iterate successively through many containers. Here is the old working version of the function using a generator named ChainedObject, whatever it is:

template<typename... Iterables>
auto chain(Iterables&&... iters)
    -> ChainObject<Iterables...>
{
    return /* ... */;
}

And the corresponding main:

int main()
{
    std::vector<int> vec = { 1, 2, 3, 4, 5 };
    std::list<int>   li  = { 6, 7, 8, 9, 10, 11, 12, 13 };
    for (auto& i: chain(vec, li))
    {
        // You can edit a range of iterables
        // as if there was only one of them.
        i *= 5;

        std::cout << i << std::endl;
    }
    return 0;
}

That main worked fine. We don't care what there is in ChainObject for the problem, so let's see it. I tried to use template templates to ensure that the different collections used had the same value_type and modified the function chain the following way:

template<typename T, template<typename...> class... Iterables>
auto chain(Iterables<T>&&... iters)
    -> ChainObject<T, Iterables...>
{
    return /* ... */;
}

I thought this would do the trick to ensure the list and vector from my previous main share a same type, but instead, I get the following error from GCC 4.7.1:

In function 'int main()':

error: no matching function for call to 'chain(std::vector&, std::list&)'

note: candidates are:

note: ChainObject<T, Iterables ...> chain(Iterables<T>&& ...) [with T = int; Iterables = {std::vector, std::list}]

note: no known conversion for argument 2 from 'std::list<int>' to 'std::list<int>&&'

note: ChainObject<T, Iterables ...> chain(Iterables<T>&& ...) [with T = int; Iterables = {std::vector, std::list}]

note: no known conversion for argument 2 from 'std::list<int>' to 'std::list<int>&&'

error: unable to deduce 'auto&' from ''

It seems that the problem comes from argument passing to the function taking rvalue references. However, I really don't understand why my first version worked fine, and note the one using template templates.

Это было полезно?

Решение

Your problem is that the T&& template magic only works for type parameters (it works by deducing T as eg. int& if needed - for lvalue arguments). It can't work for template template arguments, where the actual type is X<T>&& - X must be a class template in this case, not something like "reference-to-class-template". So in the end you have to pass a rvalue-reference, which you cannot implicitly get from a lvalue (variable).

That said, I would suggest you to revert to your earlier code and check that the value_types are the same (or compatible, etc., whatever gets you going) with SFINAE.

Rough code sketch (for strict equality):

template <class ... Ts> struct all_types_equal
{
  static const bool value = false;
};

template <class T>
struct all_types_equal<T>
{
  static const bool value = true;
};
template <class T, class ... Rest>
struct all_types_equal<T, T, Rest...>
{
  static const bool value = all_types_equal<T, Rest...>::value;
};

template<typename... Iterables>
auto chain(Iterables&&... iters)
    -> typename std::enable_if<all_types_equal<Iterable::value_type...>::value, ChainObject<Iterables...> >::type
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top