7

So I recently watched this talk on c++: https://www.youtube.com/watch?v=mFUXNMfaciE

And I was very interested on trying it out. So after some toy program I am stuck on how to properly flatten a vector of vectors into a vector. According to the documentation here: https://ericniebler.github.io/range-v3/ This is possible using ranges::view::for_each. However I just can't seem to get it to work. Here is some minimal code.

#include <range/v3/all.hpp>
#include <iostream>
#include <vector>

int main()
{
    auto nums = std::vector<std::vector<int>>{
        {0, 1, 2, 3},
        {5, 6, 7, 8},
        {10, 20},
        {30},
        {55}
    };

    auto filtered = nums
        | ranges::view::for_each([](std::vector<int> num) { return ranges::yield_from(num); })
        | ranges::view::remove_if([](int i) { return i % 2 == 1; })
        | ranges::view::transform([](int i) { return std::to_string(i); });

    for (const auto i : filtered)
    {
        std::cout << i << std::endl;
    }
}
  • What isn't working about it? – Galik Apr 24 '17 at 00:26
  • @Galik a long long list of compile errors that start with "Main.cpp: In instantiation of ‘main():: [with auto:1 = std::vector]’:" –  Apr 24 '17 at 00:30

1 Answers1

9

range-v3 error messages tend to be pretty horrible, so much so that this one is actually better than most:

prog.cc: In lambda function:
prog.cc:16:90: error: no match for call to '(const ranges::v3::yield_from_fn) (std::vector<int>&)'
         | ranges::view::for_each([](std::vector<int> num) { return ranges::yield_from(num); })
                                                                                          ^
In file included from /opt/wandbox/range-v3/include/range/v3/view.hpp:38:0,
                 from /opt/wandbox/range-v3/include/range/v3/all.hpp:21,
                 from prog.cc:1:
/opt/wandbox/range-v3/include/range/v3/view/for_each.hpp:133:17: note: candidate: template<class Rng, int _concept_requires_132, typename std::enable_if<((_concept_requires_132 == 43) || ranges::v3::concepts::models<ranges::v3::concepts::View, T>()), int>::type <anonymous> > Rng ranges::v3::yield_from_fn::operator()(Rng) const
             Rng operator()(Rng rng) const
                 ^~~~~~~~

to someone with a bit of knowledge of range-v3's concepts emulation layer, this "clearly" states that the call to yield_from failed because the type of the argument you passed to it - std::vector<int> - does not satisfy the View concept.

The View concept characterizes a subset of ranges that do not own their elements, and therefore have all operations - move/copy construction/assignment, begin, end, and default construction - computable in O(1). The range composition algrebra in range-v3 works only on views to avoid having to deal with element lifetimes and to provide predictable performance.

yield_from rejects the std::vectors you are trying to pass since they are not views, but you could easily provide views by (1) taking the vectors as lvalues instead of by value in for_each, and (2) yielding view::all of those lvalues [DEMO]:

auto filtered = nums
    | ranges::view::for_each([](std::vector<int>& num) {
        return ranges::yield_from(ranges::view::all(num)); })
    | ranges::view::remove_if([](int i) { return i % 2 == 1; })
    | ranges::view::transform([](int i) { return std::to_string(i); });

But in this simple case, flattening a range of ranges of elements into a range of elements already has a purpose-specific view in range-v3: view::join. You may as well use that [DEMO]:

auto filtered = nums
    | ranges::view::join
    | ranges::view::remove_if([](int i) { return i % 2 == 1; })
    | ranges::view::transform([](int i) { return std::to_string(i); });
Casey
  • 41,449
  • 7
  • 95
  • 125
  • 1
    amazing. Can you reccomend some docs or articles I can read that i can gain a better insight into how all of this works? –  Apr 24 '17 at 06:45
  • Also Why does yield_from only work on non-owning ranges while I can just nums unaltered into `ranges::view_for_each()`? –  Apr 24 '17 at 06:52
  • 1
    The views accept *lvalue* containers and implicitly convert them to views. The idea is that an lvalue is likely to stay alive until you use the result of view composition whereas an rvalue - which could die at the end of the full expression - is not. `yield_from` accepts views but does *not* implicitly convert lvalues to views since its typical use case is in a return statement. This prevents returning views that reference temporary containers, i.e., exactly what the program in the OP is doing. – Casey Apr 24 '17 at 15:29
  • 1
    Is there any documentation on `yield_from`? I also see `yield` and `yield_if`, but no explanation; just the `repeat_n` example...which seems to work even with the `yield_from` call removed? – user2023370 Jun 06 '19 at 17:06