为何std::swap不交换std::reference_wrapper底层对象?如何实现mysort?
mysort 的问题解答 问题背景
我尝试实现一个名为mysort的函数,它接受多个同类型参数并对其排序。例如以下代码应输出1 2 3:
template <typename ... Args> void mysort(Args& ... args); int main(int, char**) { int x = 3; int y = 1; int z = 2; mysort(x, y, z); cout << x << ' ' << y << ' ' << z << endl; return 0; }
尝试的方案
我提出的实现方案如下:
#include <array> #include <algorithm> #include <functional> template <typename T, typename ... Args> void mysort(T& t, Args& ... args) { constexpr size_t n = sizeof...(Args) + 1; std::array<std::reference_wrapper<T>, n> temp = { std::ref(t), std::ref(args)... }; std::sort(temp.begin(), temp.end()); }
但该方案无效,因为std::swap对std::reference_wrapper的操作不符合预期(我期望对std::reference_wrapper的修改能同步到其指向的底层对象)。
添加以下代码后功能恢复正常:
namespace std { void swap(std::reference_wrapper<int>& a, std::reference_wrapper<int>& b) noexcept { std::swap(a.get(), b.get()); } }
但我不确定标准是否要求std::sort必须使用std::swap,因此该方案可能依赖具体实现,并不完整。
问题总结
我原本认为std::reference_wrapper应完全表现得像引用,但实际并非如此。我的问题如下:
- 实现
mysort的最佳方式是什么? - 为何标准不为
std::reference_wrapper提供可交换底层对象的std::swap特化?
解答
1. 实现mysort的最佳方式
有几种更可靠的实现方式,避免依赖自定义的std::swap特化:
方式一:值拷贝排序后赋值回原变量
直接把参数值拷贝到数组,排序后再逐个赋值回原参数,简单且完全符合标准:
#include <array> #include <algorithm> template <typename T, typename... Args> void mysort(T& first, Args&... rest) { constexpr size_t count = 1 + sizeof...(Args); std::array<T, count> values = {first, rest...}; std::sort(values.begin(), values.end()); // 用折叠表达式将排序后的值赋值回原参数 size_t idx = 0; ((first = values[idx++]), ..., (rest = values[idx++])); }
注:该方式要求T可拷贝,这对支持排序的类型通常不是问题。
方式二:自定义交换逻辑的引用包装器方案
若要避免拷贝(比如针对大对象),可以给std::sort传递自定义交换函数,无需修改std命名空间:
#include <array> #include <algorithm> #include <functional> template <typename T, typename... Args> void mysort(T& first, Args&... rest) { constexpr size_t count = 1 + sizeof...(Args); std::array<std::reference_wrapper<T>, count> refs = {std::ref(first), std::ref(rest)...}; std::sort(refs.begin(), refs.end(), [](const auto& a, const auto& b) { return a.get() < b.get(); }, [](auto& a, auto& b) { std::swap(a.get(), b.get()); }); }
C++17及以上的std::sort支持自定义交换操作,完全符合标准规范。
如果使用C++20范围库,代码更简洁:
#include <array> #include <ranges> #include <functional> template <typename T, typename... Args> void mysort(T& first, Args&... rest) { std::array<std::reference_wrapper<T>, 1 + sizeof...(Args)> refs = {std::ref(first), std::ref(rest)...}; std::ranges::sort(refs, {}, &std::reference_wrapper<T>::get); }
通过投影&std::reference_wrapper<T>::get直接比较底层对象,std::ranges::sort会正确处理交换逻辑。
2. 为何标准不为std::reference_wrapper提供交换底层对象的std::swap特化
标准未提供该特化,是因为std::reference_wrapper的核心语义是可拷贝的引用容器,而非底层对象的代理。默认的std::swap行为是交换两个包装器的指向(让它们各自引用对方原来的对象),这才符合其"引用容器"的设计定位。
如果提供交换底层对象的特化,会与reference_wrapper的语义冲突:用户若想交换两个包装器的指向,将无法实现。标准库的设计原则是让组件行为清晰、符合语义定义,避免歧义。若需交换底层对象,显式调用std::swap(a.get(), b.get())才是正确做法。
内容的提问来源于stack exchange,提问作者arthurbesse

