泛化C++模板函数以兼容std::reference_wrapper与原生类型数组
我有两个C++模板函数:solveFbd接收N个位置向量并更新它们,forceInternal接收N个位置向量计算力。forceInternal的参数类型最初照搬solveFbd的const std::reference_wrapper<Vector3r> (&positions)[N],直接传递solveFbd的positions调用没问题,但forceInternal根本不修改positions,理想参数应该是const std::reference_wrapper<const Vector3r> (&positions)[N]——不过加这个const会触发调用错误,这是次要问题。
现在我要复制原位置向量生成新数组positionsCopy传入forceInternal,结果编译报错:无法将Vector3r[N]转换为const std::reference_wrapper<Vector3r> (&)[N]。代码库里有大量类似的forceInternal函数,不想一个个加Vector3r[N]的重载,询问能否修改现有solveFbd或forceInternal解决问题。
最小可复现代码
#include <Eigen/Dense> using Real = double; using Vector3r = Eigen::Matrix<Real, 3, 1, Eigen::DontAlign>; template<unsigned int N> using Vectorr = Eigen::Matrix<Real, N, 1, Eigen::DontAlign>; template <unsigned int N> void forceInternal(const std::reference_wrapper<Vector3r>(&positions)[N], Vectorr<N * 3>& forces) { // do some stuff } template <unsigned int N> bool solveFbd(const std::reference_wrapper<Vector3r>(&positions)[N]) { Vectorr<N * 3> forces; forceInternal(positions, forces); // 这里没问题 Vector3r positionsCopy[N]; Vector3r delta[N]; for (unsigned int p = 0; p < N; p++) { // 必须复制,不能修改原positions positionsCopy[p] = positions[p].get() + delta[p]; } forceInternal(positionsCopy, forces); // 报错行 } int main() { Vector3r x1, x2, x3; solveFbd({ x1, x2, x3 }); }
编译错误信息
Build started... 1>------ Build started: Project: ConsoleApplication4, Configuration: Debug x64 ------ 1>ConsoleApplication4.cpp 1>C:\Users\sin3point14\my-projs\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp(27,5): error C2664: '_Ret std::_Func_class<_Ret,const std::reference_wrapper<Vector3r> (&)[3],Eigen::Matrix<Real,9,1,2,9,1> &>::operator ()(const std::reference_wrapper<Vector3r> (&)[3],Eigen::Matrix<Real,9,1,2,9,1> &) const': cannot convert argument 1 from 'Vector3r [3]' to 'const std::reference_wrapper<Vector3r> (&)[3]' with [ _Ret=void ] 1>C:\Users\sin3point14\my-projs\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp(27,5): note: Reason: cannot convert from 'Vector3r [3]' to 'const std::reference_wrapper<Vector3r> [3]' 1>C:\Users\sin3point14\my-projs\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp(27,5): note: There is no context in which this conversion is possible 1>C:\Users\sin3point14\my-projs\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp(33,2): message : see reference to function template instantiation 'bool solveFbd<3>(const std::reference_wrapper<Vector3r> (&)[3])' being compiled 1>Done building project "ConsoleApplication4.vcxproj" -- FAILED. ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========== ========== Build started at 5:55 PM and took 01.610 seconds ==========
方法1:修改forceInternal接受任意可迭代范围
直接调整forceInternal的参数类型,让它兼容任何包含Vector3r(或其const引用)的可迭代对象,不管是reference_wrapper数组还是Vector3r数组都能直接传入,同时解决const正确性问题。
修改后的forceInternal:
#include <iterator> template <typename PositionsRange> void forceInternal(const PositionsRange& positions, Vectorr<std::size(positions)*3>& forces) { for (const auto& elem : positions) { // 统一转换为const Vector3r& const Vector3r& pos = std::is_reference_v<decltype(elem)> ? elem : static_cast<const Vector3r&>(elem); // 这里编写原有的处理逻辑 } }
如果需要保留N模板参数约束数量,可以改成:
template <unsigned int N, typename PositionsRange> void forceInternal(const PositionsRange& positions, Vectorr<N * 3>& forces) { static_assert(std::size(positions) == N, "位置数量不匹配"); // 处理逻辑同上 }
这种方式不需要修改任何调用点,原有代码和新的positionsCopy都能直接传参。
方法2:在solveFbd中包装positionsCopy为reference_wrapper数组
如果不想改动forceInternal,可以在调用前把positionsCopy转成std::reference_wrapper<Vector3r>数组,适配现有参数类型:
修改solveFbd代码:
template <unsigned int N> bool solveFbd(const std::reference_wrapper<Vector3r>(&positions)[N]) { Vectorr<N * 3> forces; forceInternal(positions, forces); Vector3r positionsCopy[N]; Vector3r delta[N]; for (unsigned int p = 0; p < N; p++) { positionsCopy[p] = positions[p].get() + delta[p]; } // 构造reference_wrapper数组 std::reference_wrapper<Vector3r> pos_refs[N]; for (unsigned int p = 0; p < N; p++) { pos_refs[p] = positionsCopy[p]; } forceInternal(pos_refs, forces); // 现在可正常调用 return true; }
这种方法完全不修改forceInternal,只需要在调用前加一层简单包装,代码量极小。
方法3:给forceInternal添加const版本重载,兼顾兼容性
针对你提到的理想参数类型问题,可以给forceInternal加一个const版本的重载,原有调用逻辑转到该版本,同时兼容两种reference_wrapper类型:
// 原有版本,兼容非const reference_wrapper数组,内部调用const版本 template <unsigned int N> void forceInternal(const std::reference_wrapper<Vector3r>(&positions)[N], Vectorr<N * 3>& forces) { const std::reference_wrapper<const Vector3r> const_refs[N]; for (unsigned int p = 0; p < N; p++) { const_refs[p] = positions[p]; } forceInternal(const_refs, forces); } // 理想的const版本,实现实际业务逻辑 template <unsigned int N> void forceInternal(const std::reference_wrapper<const Vector3r>(&positions)[N], Vectorr<N * 3>& forces) { // 编写处理逻辑,保证不修改positions }
这样既保留了原有代码的兼容性,又实现了const正确性。如果要传positionsCopy,同样可以用方法2的包装方式,转成const std::reference_wrapper<const Vector3r>数组再传递。
内容的提问来源于stack exchange,提问作者Sin3point14

