std::views::transform 后为何无法用 std::swap 交换元素?
当前的 u2 无法用于原地排序。转换函数按值返回 int,因此 *i 和 *j 得到的是计算结果,并非可供交换的底层元素引用。只修改 HoarePartition 中的交换调用,不能让这个视图成为可修改范围。
如果要交换原容器中的元素,视图必须提供可写的元素访问;如果要排序翻倍后的数值,应先将转换结果保存到容器中。
为什么错误信息同时出现 int 和 int&?
这个 lambda 按值接收元素,返回的乘法结果也是值:
[](auto x) { return x * 2; }
对 std::views::transform 的迭代器解引用时,会调用转换函数;结果不会被存回 v。
对于题目中的 u2:
auto u2 = v | std::views::transform([](auto x) {
return x * 2;
});
表达式 *std::ranges::begin(u2) 是一个 int 类型的纯右值。可以用类型断言说明这一点:
#include <type_traits>
static_assert(std::is_same_v<
std::ranges::range_reference_t<decltype(u2)>,
int
>);
range_reference_t 表示迭代器解引用表达式的类型,名称中的 reference 不代表它一定是 C++ 引用类型。
用于普通对象交换的 std::swap 重载,其接口可以简化为:
template <class T>
void swap(T& a, T& b);
传入两个 int 计算结果时,模板参数可以推导为 int,但参数随后需要绑定到 int&。非 const 左值引用不能绑定纯右值,所以调用失败。
错误信息中的 int 描述实参,int& 描述候选函数要求的参数类型,两者并不矛盾。
HoarePartition 中应该怎样交换?
在基于迭代器的 ranges 算法中,可以用下面的接口交换两个位置上的元素:
std::ranges::iter_swap(i, j);
也就是将原来的:
std::swap(*i, *j);
替换为迭代器交换接口。std::ranges::iter_swap 能处理符合要求的迭代器及其交换定制,比直接假设解引用结果能够传给 std::swap 更适合泛型算法。
不过,这仍然要求两个位置上的元素可以交换。题目中的转换函数返回普通 int 值,没有可写引用或可写代理,因此这项替换仍不能让 quicksort(u2) 成立。
下面的写法也不能解决问题:
auto a = *i;
auto b = *j;
std::swap(a, b);
它只交换两个局部变量,不会改变范围中的元素。
根据排序目标调整输入
交换原容器中的元素
如果转换操作本来就应该选择某个可修改对象,可以让 lambda 显式返回引用。下面的例子直接返回原元素:
auto u3 = v | std::views::transform(
[](auto& x) -> auto& {
return x;
}
);
quicksort(u3);
此时 u3 的迭代器解引用得到 int&,满足交换操作所需的引用条件。通过它交换元素会修改 v。
返回类型中的 -> auto& 不能随意省略:
[](auto& x) { return x; } // 按值返回
[](auto& x) -> auto& { return x; } // 按引用返回
这个示例改变了原先“读取时乘以 2”的语义。如果不需要转换,可以直接对 v 或 std::views::all(v) 排序。
对于 x * 2 这样的计算表达式,不能简单地添加引用返回类型来获得一个有效、可写的结果对象。
保存并排序翻倍后的数值
如果目标是得到一个包含翻倍结果的有序容器,可以先物化转换结果:
#include <algorithm>
#include <iterator>
#include <ranges>
#include <vector>
std::vector<int> v = {3, 2, 4, 1, 5};
auto doubled = v | std::views::transform([](int x) {
return x * 2;
});
std::vector<int> values;
values.reserve(v.size());
std::ranges::copy(doubled, std::back_inserter(values));
std::ranges::sort(values);
// values: {2, 4, 6, 8, 10}
values 拥有实际存储的元素,可以交换、赋值和排序。示例使用 C++20 接口,不依赖 std::ranges::to。
只在比较时使用转换结果
如果想按 x * 2 的结果排列原始元素,可以使用 projection:
std::ranges::sort(
v,
std::ranges::less{},
[](int x) { return x * 2; }
);
projection 生成比较用的值;排序时交换的仍是 v 中的原始元素,元素不会被替换成翻倍后的数值。
为什么 std::views::reverse 可以使用?
对于题目中可修改的 std::vector<int>,std::views::reverse 改变遍历顺序,但反向迭代器解引用后仍得到底层元素的 int&,因此交换能够修改原容器。
std::views::transform 的解引用结果由转换函数的返回类型决定:返回值时得到计算结果,返回引用时才可能访问可修改的原对象。
如果继续维护这套泛型 quicksort,可以使用 std::sortable 等约束,让不支持排序的范围在函数入口处被拒绝。原代码还使用了迭代器加减、ranges::size 和同类型的结束位置,因此也需要满足相应的随机访问、大小及迭代器边界条件。满足这些接口要求后,分区逻辑和递归边界的正确性仍需要由算法本身保证。
备注:内容仅供参考。
