std::ranges::to 是否允许转换为 std::map?

Does std::ranges::to allow converting to a std::map?

std::ranges::to 论文 wg21.link/p1206 中,概述部分具有以下内容

//Supports converting associative container to sequence containers
auto f = ranges::to<vector>(m);

但是我找不到在论文的其余部分描述转换为 std::map 的细节的地方。我尝试了 range-v3 和 Sy Brand 在 https://github.com/TartanLlama/ranges 中对 ranges::to 的实现,但它们都无法编译将范围转换为 std::map 的代码。那么这只是这些库中缺少的,还是正在转换为 std::map 并非真正打算允许的?

So is this just missing from those libraries or is converting to a std::map not really intended to be allowed?

std::map 到 std::vector:

根据[range.utility.conv.to]的描述,这个

map<int, double> m;
auto f = ranges::to<vector>(m);

将调用以下重载

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)...).

其中CvectorRmap<int, double>&Args...为空参数包。请注意,C++23 为 vector

引入了以下 range version constructor
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.

及以下 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>;

所以C(from_range, declval<R>())是一个有效的表达式,DEDUCE_EXPR的类型将是vector<pair<const int, double>>,这将进一步调用

to<vector<pair<const int, double>>>(m);

这将通过范围版本构造函数构造一个vector,其中value_typepair<const int, double>

所以C++23中的ranges::to<vector>(m)基本上等同于

map<int, double> m;
vector f(m.begin(), m.end());

之所以range-v3 fails是因为它的内部实现检测到vector<pair<const int, double>>是可预留的,所以会先默认构造vector然后调用v.reserve()到pre-allocate 内存,然后通过调用 v.assign() 复制 map,但由于 pair<const int, double> 不可复制分配,因此编译失败。

我怀疑这是 range-v3 的实现错误,因为如果 vectorreserve() 函数不存在它会编译,并且这个优化的重载似乎并不限制 value_type 必须是 copy-assignable.

std::vector 到 std::map:

以及以下

auto g = ranges::to<map>(f);

因为 std::map 在 C++23

中也有 following constructors 和相应的 CATD
template<container-compatible-range<value_type> R>
  map(from_range_t, R&& rg, const Compare& comp = Compare(), const Allocator& = Allocator());

所以按照同样的游戏规则,我们会得到一个map<int, double>.

Does std::ranges::to allow converting to a std::map?

是的。

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

我还没有尝试过 Sy 的实现,看起来 range-v3 的实现很奇怪:

#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);
}

问题是 range-v3 中的 class 模板直接调用版本出于某种原因专门尝试实例化 C<range_value_t<R>>(在本例中为 std::map<std::pair<int, int>>,显然是错误的) 即使这里已经有一个元函数可以做正确的事情并且会推断 std::map<int, int> (由管道版本使用)。

标准库中的 ranges::to 指定这两个做同样(正确)的事情,所以这将在 C++23 中工作。这只是 range-v3 中的一个简单错误修复。