c++std-ranges

How to obtain a range to loop over containers in variants


I have the need to map some elements to certain ids, elementId's to starting node ids in a graph. I need to store just ids of the elements so generally we could work with just sets or vectors of size_t. Generally there are millions of elements that are added in batches to be mapped to a single id. This is why we use specific representations for typical assignments and store it in a variant.

For example

//std::set -> random set of elements
//Interval -> elements from a starting id up to a final id
//Single -> just a single element 
using VariantSet = std::variant<std::set, Interval, Single>;

//This is the vector containing the mapping
std::vector< std::tuple<VariantSet, size_t> > mapping;

We started using std::ranges on several parts and the general result have been great, but i got stuck with the variant. I'd like to get a range from a function so I could iterate over the elements that are mapped to a given id.

For example:

for (auto elementId: getMappedElements(mapping, interestingId){
   do_something_with_the_element(elementId);
}

The problem I found is that i have to use std::visit to retrieve the range from the underlying object stored in the variant, but that range will be a different type depending on which real container is iterated. For example the Singular container was returning views::single while the Interval was returning iota. Of course, this enrages the compiler as the visitors must return the same type for every possible type in the variant and of course the wrapping function getMappedElements can't return multiple types either.

Currently we are passing the function "do_something_with_the_element" as a lambda so we can do a visitor that in turns applies this lambda, but it messes up some uses that we had in mind. Is there a way to achieve this "polymorphic" range so i can actually return a range that later can be further composed (very likely a transform)

Any help is appreciated.

EDIT: Link to example: https://godbolt.org/z/nx41v3Eqb . It was buried in the comments, copied here for better visibility.


Solution

  • Since you should rely on variant::visit, the question is then how to provide each iterated item without having to process it through some lambda.

    Since c++20, coroutines could be useful in your use case: you normally use std::visit and according to the variant types, you call co_yield for each item to be iterated. A possible implementation of a dedicated mygenerator in your case would be:

    using sometype = int;
    
    // We define our variant type.
    using VariantSet = std::variant<
        std::set<sometype>,
        std::vector<sometype>,
        sometype
    >;
    
    auto mygenerator (VariantSet& v)
    {
        // Each visit-lambda has to explicit its return type.
        // Note that `std::generator` is available since `c++23`.
        using result_t = std::generator<sometype>;
    
        // for 'overloaded', see https://en.cppreference.com/w/cpp/utility/variant/visit2
        return std::visit (overloaded {
            [](std::set<sometype>    const& arg) -> result_t { for (auto const& x : arg)  { co_yield x; } },
            [](std::vector<sometype> const& arg) -> result_t { for (auto const& x : arg)  { co_yield x; } },
            [](sometype              const& arg) -> result_t { co_yield arg; }
        }, v);
    }
    

    You can use it with

    int main ()
    {
        std::set   <sometype> x = {2,4,6,8,10};
        std::vector<sometype> y = {1,3,5,7};
        sometype              z = 42;
    
        VariantSet v;
    
        v = x;    for (auto const& i : mygenerator(v)) {  std::cout << i << " ";  }  std::cout << "\n";
        v = y;    for (auto const& i : mygenerator(v)) {  std::cout << i << " ";  }  std::cout << "\n";
        v = z;    for (auto const& i : mygenerator(v)) {  std::cout << i << " ";  }  std::cout << "\n";
    }
    

    Demo

    I think that you could pipe such a generator with some std::views.

    Note however that using coroutines this way may incur some overheads (see this and this).