0

C++20 introduces the views::elements, views::keys and views::values to easily deal with range of tuple-like values:

std::vector v{std::tuple{'A', 1}, {'B', 2}, {'C', 3}};
auto it = std::ranges::find(v | std::views::elements<0>, 'B');
assert(*it == 'B');

After applying the adaptor, v | std::views::elements<0> become a range of the first element of each tuple, so the return type of the ranges::find is the iterator type of that transformed range.

But is there a possible way to transform it back to the origin iterator type to get the origin tuple?

assert(*magic_revert(it) == std::tuple{'B', 2});
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • Please add the c++ tag to C++ questions. More users will see your question that way. – cigien Jan 27 '21 at 03:52
  • Perhaps `std::ranges::find_if` to find all elements in the vector whose first tuple-elements equals `*it`? Or if you're just interested in finding the first (or only, if that's the case) then just `std::find_if`? – Some programmer dude Jan 27 '21 at 04:02
  • Or maybe a vector of tuples is the wrong data-structure here? Perhaps a `std::map` (or `std::unordered_map`) would be a better fit? – Some programmer dude Jan 27 '21 at 04:04
  • @Some programmer dude. I just want to know if there is a possible way to revert the transformed iterator. Since `std::ranges::find(v, 'B', [](const auto& elem) { return std::get<0>(elem); });` will return the origin iterator. – 康桓瑋 Jan 27 '21 at 04:16
  • Well yes, because you're doing the `find` on `v`. If you do the `find` on `v | views::something`, you'll get an iterator into that view, not `v`. – cigien Jan 27 '21 at 04:17

2 Answers2

2

There's no real way to get from it to the original tuple, since it points into a view constructed from the original range of tuples.

You can work around this quite easily though:

auto elems = v | std::views::elements<0>;  // name the view

auto it = std::ranges::find(elems, 'B');   // find it

// use the distance of it from the beginning of elems, to get an iterator into v
auto orig_it = std::next(std::begin(v), 
                         std::distance(std::begin(elems), it));
cigien
  • 57,834
  • 11
  • 73
  • 112
2

It's possible to get an iterator to the underlying range by calling .base().

assert(*it.base() == std::tuple{'B', 2});

But it might be more idiomatic to use a projection with std::ranges::find.

std::vector v{std::tuple{'A', 1}, {'B', 2}, {'C', 3}};
auto it = std::ranges::find(v, 'B', [](auto& e) { return std::get<0>(e); });
assert(*it == std::tuple{'B', 2});
cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • Never thought that there a `base()` function here. – 康桓瑋 Jan 27 '21 at 06:23
  • This is very nice. Is `.base` always valid for views? e.g. I don't imagine it would work if the view didn't have an underlying container it was viewing. Of course, that's not a concern for the OP, I'm just curious. – cigien Jan 27 '21 at 06:54
  • Most adaptors define in `` have the [`base()`](https://en.cppreference.com/w/cpp/ranges/transform_view) function which returns the underlying range. Same with their [`iterator`](https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/ranges#L1391). – 康桓瑋 Jan 27 '21 at 07:02