3

Having any range as an input, how to first convert its elements to tuples and then save them in a std::map with ranges::to?

This code works and creates a vector of tuples:

#include <range/v3/all.hpp>
#include <fmt/format.h>
#include <fmt/ranges.h>

int main() { 
        auto v = ranges::views::iota(3, 10)
                | ranges::views::transform([](auto const &v){ return std::make_tuple(v, v*2); })
                | ranges::to<std::vector>();    
        fmt::print("{}\n", v);
}

Replacing vector by map, I'd expect to receive a map instead (with first elements of the tuples becoming keys and second elements becoming their values), but the code doesn't compile:

#include <range/v3/all.hpp>
#include <fmt/format.h>
#include <fmt/ranges.h>

int main() { 
        auto v = ranges::views::iota(3, 10)
                | ranges::views::transform([](auto const &v){ return std::make_tuple(v, v*2); })
                | ranges::to<std::map>();       
        fmt::print("{}\n", v);
}

I get: test.cpp:11:10: error: invalid operands to binary expression ('invoke_result_t<ranges::views::transform_base_fn, ranges::iota_view<int, int>, (lambda at test.cpp:9:30)>' (aka 'transform_view<ranges::iota_view<int, int>, (lambda at test.cpp:9:30)>') and 'detail::to_container_fn<detail::from_range<std::map>>' (aka 'closure<ranges::detail::from_range<std::map>, ranges::detail::to_container::fn<ranges::detail::from_range<std::map>>>'))

Notes: I didn't find many examples of using ranges::to<map> in the internet, but in this answer: https://stackoverflow.com/a/74433668 there's working code where the result of ranges::views::zip is converted to a map. Since zip produces "tuple-like" elements, I expected my code to work too, but apparently it's not that straightforward.

Compiler is Clang++ v. 15.0.6, ranges is current master.

notsurewhattodo
  • 446
  • 4
  • 11

1 Answers1

1

You need a std::pair, not a std::tuple (working example):

auto m = ranges::views::iota(3, 10)
       | ranges::views::transform([](auto const &v){ return std::make_pair(v, v*2); })
       | ranges::to<std::map>;

After all, while something like this is fine,

std::vector<std::pair<int, int>> v;
std::map<int,int> M(v.begin(), v.end());

something like this is not

std::vector<std::tuple<int, int>> v;
std::map<int,int> M(v.begin(), v.end());

Or, more simply as suggested in a comment,

std::map<int,int> P{{std::pair<int, int>{}}};  // OK
std::map<int,int> T{{std::tuple<int, int>{}}}; // Error
Enlico
  • 23,259
  • 6
  • 48
  • 102
  • Thanks, this principially helps. Two things however: (1) it only works for me if I explicitly specify map template arguments (`ranges::to>`) and (2) I'd be very interested to know, why can't tuple be used. I'm accepting the answer though, because it solves my problem. – notsurewhattodo Apr 06 '23 at 18:39
  • 1
    As regards (1), mine is a solution that works for C++17 on all 3 compilers, and you don't need to specify the template arguments; check your compiler versions and options. Concerning (2), it must have to do with some specilization or viability of conversion. – Enlico Apr 06 '23 at 18:51
  • 1
    @Enlico regarding point number two, I believe it's because `std::map` uses a `std::pair`, not a tuple – CoffeeTableEspresso Apr 06 '23 at 18:56
  • @Enlico Indeed, switching to C++17 removes the requirement of template arguments specification. – notsurewhattodo Apr 06 '23 at 19:02
  • 1
    @notsurewhattodo, yeah, CTAD is a C++17 feature. – Enlico Apr 07 '23 at 07:35
  • 1
    @CoffeeTableEspresso, I know that `sdt::map` uses `std::pair` but for some reason I gave for granted it was constructible from 2-`std::tuple`s too, because, after all, there's no reason for it not to offer that possibility through the API. – Enlico Apr 07 '23 at 07:37