实现可用于STL算法的单绑定reference_wrapper类
问题背景
正如你所说,你想把矩阵元素的引用存入vector,用STL的std::rotate操作同步修改原始矩阵数据,但自定义的single_bind_reference_wrapper类存在几个问题,导致编译报错。先来看你的代码和错误信息:
你的实现代码
#include <vector> #include <iostream> #include <algorithm> using Matrix = std::vector<std::vector<int>>; /** * Class implementing std::reference_wrapper that * cannot be rebound after creation. * **/ template <class T> class single_bind_reference_wrapper { // pointer to the original element T *p_; public: // typedefs using type = T; // construct/copy/destroy single_bind_reference_wrapper(T& ref) noexcept : p_(std::addressof(ref)) {} single_bind_reference_wrapper(T&&) = delete; // Enable implicit convertsion from ref<T> to ref<const T>, // or ref<Derived> to ref<Base> template <class U, std::enable_if_t<std::is_convertible<U*, T*>{}, int> = 0> single_bind_reference_wrapper(const single_bind_reference_wrapper<U>& other) noexcept : p_(&other.get()) { } // assignment template <class U> decltype(auto) operator=(U &&u) const noexcept(noexcept(std::declval<T>() = std::forward<U>(u))) { return get() = std::forward<U>(u); } // access operator T& () const noexcept { return *p_; } T& get() const noexcept { return *p_; } }; void rotate_mat (Matrix &mat, int r){ auto m = mat.size(); // Number of rows auto n = mat[0].size(); // Number of columns auto n_rings = std::min(m,n)/2; // Number of rings for(auto ring_i=0; ring_i<n_rings; ++ring_i){ // The elements of the ring are stored sequentially // in v_ring so it can be rotated with std::rotate std::vector<single_bind_reference_wrapper<int>> v_ring; std::vector<int*> v_ring_ptr; // Top side of the ring for(auto j=ring_i; j<=(n-1)-ring_i; ++j) { v_ring.push_back(mat[ring_i][j]); } // Right side of the ring for(auto i=ring_i+1; i<=(m-1)-ring_i; ++i) { v_ring.push_back(mat[i][(n-1)-ring_i]); } // Bottom size of the ring for(auto j=(n-1)-ring_i-1; j>ring_i; --j) { v_ring.push_back(mat[(m-1)-ring_i][j]); } // Left size of the ring for(auto i=(m-1)-ring_i; i>ring_i; --i) { v_ring.push_back(mat[i][ring_i]); } v_ring[0] = 10; // compilation error! // This would be my goal: //std::rotate(v_ring.begin(),v_ring.begin()+r%v_ring.size(),v_ring.end()); } }; Matrix read_matrix(int m, int n) { Matrix mat; mat.reserve(m); for(auto i=0; i<m; ++i) { mat.push_back(std::vector<int>{}); mat[i].reserve(n); for(auto j=0; j<n; ++j) { int x; std::cin >> x; mat[i].push_back(x); } } return mat; }; void print_matrix(Matrix &mat){ for (auto& i : mat){ for (auto& j : i) { std::cout << j << " "; } std::cout << "\n"; } }; int main() { int m,n; std::cin >> m >> n; int r; std::cin >> r; auto mat = read_matrix(m,n); rotate_mat(mat,r); print_matrix(mat); return 0; }
编译错误信息
solution.cc: 在函数‘void rotate_mat(Matrix&, int)’中: solution.cc:72:21: 警告:ISO C++认为这些重载存在歧义,尽管第一个的最差转换优于第二个: v_ring[0] = 10; ^~ solution.cc:34:20: 候选1:decltype(auto) single_bind_reference_wrapper<T>::operator=(U&&) const [with U = int; T = int] decltype(auto) operator=(U &&u) const ^~~~~~~~ solution.cc:13:7: 候选2:constexpr single_bind_reference_wrapper<int>& single_bind_reference_wrapper<int>::operator=(single_bind_reference_wrapper<int>&&) class single_bind_reference_wrapper { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ solution.cc:72:21: 警告:ISO C++认为这些重载存在歧义,尽管第一个的最差转换优于第二个: v_ring[0] = 10; ^~ solution.cc:34:20: 候选1:decltype(auto) single_bind_reference_wrapper<T>::operator=(U&&) const [with U = int; T = int] decltype(auto) operator=(U &&u) const ^~~~~~~~ solution.cc:13:7: 候选2:constexpr single_bind_reference_wrapper<int>& single_bind_reference_wrapper<int>::operator=(const single_bind_reference_wrapper<int>&) class single_bind_reference_wrapper { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ solution.cc: 在‘decltype(auto) single_bind_reference_wrapper<T>::operator=(U&&) const [with U = int; T = int]’的实例化中: solution.cc:72:21: 要求在此处实例化 solution.cc:35:47: 错误:将xvalue(右值引用)用作左值 noexcept(noexcept(std::declval<T>() = std::forward<U>(u))) { ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
错误分析与修复方案
我来逐个拆解这些问题:
1. 转换运算符声明错误
你的代码里的转换运算符少了operator关键字,这是语法错误:
// 错误写法 T& () const noexcept { return *p_; } // 正确写法 operator T& () const noexcept { return *p_; }
这个错误会导致wrapper无法隐式转换为引用类型,很多依赖转换的操作都会失效。
2. 赋值运算符重载歧义
编译器会自动为类生成拷贝赋值运算符和移动赋值运算符,这两个默认生成的运算符和你自定义的模板operator=产生了歧义——当你执行v_ring[0] = 10时,编译器不知道该选哪个重载。
因为你的类是单绑定的引用包装,不应该允许赋值wrapper本身(只能赋值给它引用的原始对象),所以我们需要显式删除这两个自动生成的运算符:
// 显式删除拷贝赋值和移动赋值,禁止修改wrapper本身 single_bind_reference_wrapper& operator=(const single_bind_reference_wrapper&) = delete; single_bind_reference_wrapper& operator=(single_bind_reference_wrapper&&) = delete;
3. noexcept表达式中的左值错误
std::declval<T>()返回的是T&&(右值引用,属于xvalue),而赋值操作需要左值。你应该用std::declval<T&>()来获取左值引用,这样才能合法地进行赋值:
// 修正后的赋值运算符 template <class U> decltype(auto) operator=(U &&u) const noexcept(noexcept(std::declval<T&>() = std::forward<U>(u))) { return get() = std::forward<U>(u); }
修复后的完整代码
#include <vector> #include <iostream> #include <algorithm> #include <utility> // 确保std::forward可用 using Matrix = std::vector<std::vector<int>>; /** * Class implementing std::reference_wrapper that * cannot be rebound after creation. * **/ template <class T> class single_bind_reference_wrapper { // pointer to the original element T *p_; public: // typedefs using type = T; // construct/copy/destroy single_bind_reference_wrapper(T& ref) noexcept : p_(std::addressof(ref)) {} single_bind_reference_wrapper(T&&) = delete; // Enable implicit convertsion from ref<T> to ref<const T>, // or ref<Derived> to ref<Base> template <class U, std::enable_if_t<std::is_convertible<U*, T*>{}, int> = 0> single_bind_reference_wrapper(const single_bind_reference_wrapper<U>& other) noexcept : p_(&other.get()) { } // 显式删除拷贝赋值和移动赋值,禁止修改wrapper本身 single_bind_reference_wrapper& operator=(const single_bind_reference_wrapper&) = delete; single_bind_reference_wrapper& operator=(single_bind_reference_wrapper&&) = delete; // assignment template <class U> decltype(auto) operator=(U &&u) const noexcept(noexcept(std::declval<T&>() = std::forward<U>(u))) { return get() = std::forward<U>(u); } // access operator - 修正转换运算符 operator T& () const noexcept { return *p_; } T& get() const noexcept { return *p_; } }; void rotate_mat (Matrix &mat, int r){ auto m = mat.size(); // Number of rows auto n = mat[0].size(); // Number of columns auto n_rings = std::min(m,n)/2; // Number of rings for(auto ring_i=0; ring_i<n_rings; ++ring_i){ // The elements of the ring are stored sequentially // in v_ring so it can be rotated with std::rotate std::vector<single_bind_reference_wrapper<int>> v_ring; // Top side of the ring for(auto j=ring_i; j<=(n-1)-ring_i; ++j) { v_ring.push_back(mat[ring_i][j]); } // Right side of the ring for(auto i=ring_i+1; i<=(m-1)-ring_i; ++i) { v_ring.push_back(mat[i][(n-1)-ring_i]); } // Bottom size of the ring for(auto j=(n-1)-ring_i-1; j>ring_i; --j) { v_ring.push_back(mat[(m-1)-ring_i][j]); } // Left size of the ring for(auto i=(m-1)-ring_i; i>ring_i; --i) { v_ring.push_back(mat[i][ring_i]); } // 现在可以正常赋值了 v_ring[0] = 10; // 你的目标操作现在也能正常执行了 auto rotate_step = r % v_ring.size(); std::rotate(v_ring.begin(), v_ring.begin() + rotate_step, v_ring.end()); } }; Matrix read_matrix(int m, int n) { Matrix mat; mat.reserve(m); for(auto i=0; i<m; ++i) { mat.push_back(std::vector<int>{}); mat[i].reserve(n); for(auto j=0; j<n; ++j) { int x; std::cin >> x; mat[i].push_back(x); } } return mat; }; void print_matrix(Matrix &mat){ for (auto& i : mat){ for (auto& j : i) { std::cout << j << " "; } std::cout << "\n"; } }; int main() { int m,n; std::cin >> m >> n; int r; std::cin >> r; auto mat = read_matrix(m,n); rotate_mat(mat,r); print_matrix(mat); return 0; }
验证说明
修复后,std::rotate可以正常对v_ring中的引用包装器进行操作,因为std::rotate内部的元素移动/赋值会通过我们的operator=传递到原始矩阵的元素上,从而实现同步修改原始数据的目标。
内容的提问来源于stack exchange,提问作者Blasco
相关产品推荐
相关产品推荐

