In the std::ranges::to
paper wg21.link/p1206 ths overview section has the following
//Supports converting associative container to sequence containers
auto f = ranges::to<vector>(m);
However I can't find where the detail of converting to a std::map
is descibed in the rest of the paper. I tried range-v3 and Sy Brand's implementation of ranges::to
in https://github.com/TartanLlama/ranges and neither of them compiles code converting a range to a std::map
. So is this just missing from those libraries or is converting to a std::map
not really intended to be allowed?
CodePudding user response:
So is this just missing from those libraries or is converting to a
std::map
not really intended to be allowed?
std::map to std::vector:
According to the description of [range.utility.conv.to], this
map<int, double> m;
auto f = ranges::to<vector>(m);
will invoke the following overload
template<template<class...> class C, input_range R, class... Args> constexpr auto to(R&& r, Args&&... args);
Let
DEDUCE_EXPR
be defined as follows:
C(declval<R>(), declval<Args>()...)
if that is a valid expression,- otherwise,
C(from_range, declval<R>(), declval<Args>()...)
if that is a valid expression,- otherwise,
C(declval<input-iterator>(), declval<input-iterator>(), declval<Args>()...)
if that is a valid expression,- otherwise, the program is ill-formed.
Returns:
to<decltype(DEDUCE_EXPR)>(std::forward<R>(r), std::forward<Args>(args)...)
.
Where C
is vector
, R
is map<int, double>&
, and Args...
is empty parameter pack. Note that C 23 introduces the following range version constructor for vector
template<container-compatible-range<T> R> constexpr vector(from_range_t, R&& rg, const Allocator& = Allocator());
Effects: Constructs a vector object with the elements of the range
rg
, using the specified allocator.
and the following CTAD
template<ranges::input_range R, class Allocator = allocator<ranges::range_value_t<R>>>
vector(from_range_t, R&&, Allocator = Allocator())
-> vector<ranges::range_value_t<R>, Allocator>;
So C(from_range, declval<R>())
is a valid expression, and the type of DEDUCE_EXPR
will be vector<pair<const int, double>>
, which will further invoke
to<vector<pair<const int, double>>>(m);
which will construct a vector
with value_type
of pair<const int, double>
through the range version constructor.
So ranges::to<vector>(m)
in C 23 is basically equivalent to
map<int, double> m;
vector f(m.begin(), m.end());
The reason range-v3 fails is that its internal implementation detects that vector<pair<const int, double>>
is reservable, so it will first default construct the vector
and call v.reserve()
to pre-allocate the memory, and then copy the map
by calling v.assign()
, but since pair<const int, double>
is not copy assignable, so compilation fails.
I suspect this is an implementation bug of range-v3, since it would compile if the vector
's reserve()
function didn't exist, and this optimized overload doesn't seem to constrain that the value_type
must be copy-assignable.
std::vector to std::map:
And for the following
auto g = ranges::to<map>(f);
Since std::map
also has the following constructors and corresponding CATD in in C 23
template<container-compatible-range<value_type> R>
map(from_range_t, R&& rg, const Compare& comp = Compare(), const Allocator& = Allocator());
So following the same rules of the game, we will get a map<int, double>
.
CodePudding user response:
Does
std::ranges::to
allow converting to astd::map
?
Yes.
I tried range-v3 and Sy Brand's implementation of
ranges::to
in https://github.com/TartanLlama/ranges and neither of them compiles code converting a range to astd::map
I haven't tried Sy's implementation, and it looks like range-v3's implementation is weirdly broken:
#include <map>
#include <vector>
#include <range/v3/range/conversion.hpp>
int main() {
std::vector<std::pair<int, int>> v = {{1, 2}, {3, 4}};
// this works (explicit)
// m1 is a std::map<int, int>
auto m1 = ranges::to<std::map<int, int>>(v);
// this works (deduced with a pipe)
// m2 is a std::map<int, int>
auto m2 = v | ranges::to<std::map>();
// but this does not (deduced, direct call)
auto m3 = ranges::to<std::map>(v);
}
The issue is that the class template direct call version in range-v3 for some reason specifically tries to instantiate C<range_value_t<R>>
(which would be std::map<std::pair<int, int>>
in this case, clearly wrong) even though there is a metafunction here already that does the right thing and would deduce std::map<int, int>
(used by the pipe version).
The ranges::to
in the standard library specifies these two to do the same (correct) thing, so this will work in C 23. This is just an easy bug fix in range-v3.