Vari
Vari

Reputation: 33

Constructing an iterator_range with variadic templates and runtime indices

I have a collection of equally-sized vectors and want to provide an interface for the user to obtain an iterator range over a subset of these vectors.

The following example shows the problematic line inside getRange: its idea is to receive a bunch of types (specifying the types of vectors) and equally many indices (specifying the locations of the vectors). The code compiles, but the problem is that i++ never gets executed as intended, i.e., the call is always with just i (which equals 0). This will also lead to runtime errors via boost::get if the user tries to get distinct types.

This is probably a well-known issue. What's a neat solution to it?

#include <vector>
#include <boost/variant.hpp>
#include <boost/range/combine.hpp>

template <typename... T>
struct VectorHolder
{
    template<typename X>
    using Iterator = typename std::vector<X>::const_iterator;

    std::vector<boost::variant<std::vector<T>...> > vecs_;

    template <typename X>
    auto begin(int idx) const {
        return boost::get<std::vector<X> >(vecs_.at(idx)).cbegin();
    }

    template <typename X>
    auto end(int idx) const {
        return boost::get<std::vector<X> >(vecs_.at(idx)).cend();
    }
};

template <typename... T, typename VectorHolder>
auto getRange(const VectorHolder& vh, const std::vector<int>& idx)
{
    assert(sizeof...(T) == idx.size());
    // Fetch a boost::iterator_range over the specified indices
    std::size_t i = 0;
    std::size_t j = 0;

    // PROBLEM: i and j not incremented as intended
    return boost::combine(
        boost::iterator_range<VectorHolder::Iterator<T>>(
            vh.begin<T>(idx[i++]), vh.end<T>(idx[j++]))...);
}

int main()
{
    VectorHolder<bool, int, double> vh;
    vh.vecs_.push_back(std::vector<int>(5, 5));
    vh.vecs_.push_back(std::vector<bool>(5));
    vh.vecs_.push_back(std::vector<double>(5, 2.2));
    vh.vecs_.push_back(std::vector<int>(5, 1));

    const std::vector<int> idx = { 0, 3 };
    for (auto t : getRange<int, int>(vh, idx))
    {
        std::cout << t.get<0>() << " " << t.get<1>() << "\n";
    }
}

Upvotes: 0

Views: 53

Answers (1)

Jarod42
Jarod42

Reputation: 217478

std::index_sequence helps:

template <typename... Ts, typename VectorHolder, std::size_t ... Is>
auto getRange(const VectorHolder& vh, const std::vector<int>& idx, std::index_sequence<Is...>)
{
    assert(sizeof...(Ts) == idx.size());

    return boost::combine(
        boost::iterator_range<typename VectorHolder::template Iterator<Ts>>(
            vh.template begin<Ts>(idx[Is]), vh.template end<Ts>(idx[Is]))...);
}

template <typename... Ts, typename VectorHolder>
auto getRange(const VectorHolder& vh, const std::vector<int>& idx)
{
    return getRange<Ts...>(vh, idx, std::index_sequence_for<Ts...>());
}

Demo

Upvotes: 1

Related Questions