如何在C++中实现make_rv函数:将左/右值转为右值
问题
我想实现一个模板函数make_rv,功能类似std::forward但逻辑不同:接收左值时拷贝生成右值,接收右值时直接转发为右值。使用场景如下:
template<typename T> void func(T&& arg) { f_take_rv(make_rv<T>(arg)); // f_take_rv仅接受右值 }
我尝试了两种实现,但都存在问题:
第一种实现(总是匹配左值版本)
template<typename T> // for lvalue constexpr T make_rv(typename std::remove_reference<T>::type& arg) { return arg; } template<typename T> // for rvalue constexpr T&& make_rv(typename std::remove_reference<T>::type&& arg) { return static_cast<T&&>(arg); }
问题:无论传入左值还是右值,都会匹配第一个左值版本。
第二种实现(字符串字面量报错)
使用std::enable_if控制类型推导,但传入字符串字面量(如"str")时会报错:no matching function for call to 'make_rv<const char(&)[4]>(const char [4])'
template<typename T> constexpr typename std::enable_if< std::is_reference<T>::value, typename std::remove_reference<T>::type >::type make_rv(typename std::remove_reference<T>::type& arg) { return arg; } template<typename T> constexpr typename std::enable_if< !std::is_reference<T>::value, T >::type&& make_rv(typename std::remove_reference<T>::type&& arg) { return static_cast<T&&>(arg); }
附GCC的std::forward实现作为参考:
template<typename _Tp> // forwarding an lvalue constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } template<typename _Tp> // forwarding an rvalue constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { return static_cast<_Tp&&>(__t); }
正确实现方案
核心思路是利用模板参数的引用折叠规则,结合std::remove_reference区分左值和右值场景,同时避免类型推导失败的问题。
最终实现代码
简洁通用版(C++11及以上)
#include <type_traits> template<typename T> constexpr typename std::remove_reference<T>::type&& make_rv(T&& arg) noexcept { return static_cast<typename std::remove_reference<T>::type&&>( std::forward<T>(arg) ); }
直观分支版(C++17及以上)
#include <type_traits> template<typename T> constexpr auto make_rv(T&& arg) noexcept -> decltype(std::forward<T>(arg)) { if constexpr (std::is_lvalue_reference_v<T>) { // 左值场景:拷贝后转为右值 return std::move(static_cast<typename std::remove_reference_t<T>>(arg)); } else { // 右值场景:直接转发 return std::forward<T>(arg); } }
实现解释
简洁通用版原理:
- 传入左值时,
T被推导为左值引用类型(如int&),std::remove_reference<T>::type为int,最终返回int&&;std::forward<T>(arg)等价于左值,拷贝后转为右值。 - 传入右值时,
T被推导为非引用类型(如int),std::remove_reference<T>::type仍为int,返回int&&;std::forward<T>(arg)直接转发原右值。
- 传入左值时,
直观分支版原理:
- 用
if constexpr在编译期分支处理:左值引用分支通过拷贝+std::move生成右值;右值分支直接转发。逻辑更清晰,可读性更强。
- 用
旧实现失败原因
- 第一种实现:函数参数中的
typename std::remove_reference<T>::type&属于非推导上下文,调用时必须显式指定T,但func中传入的T可能是左值引用类型,导致第一个模板永远被优先匹配。 - 第二种实现:同样存在非推导上下文问题,且对数组类型(如字符串字面量
const char[4])处理有误——T推导为const char(&)[4]时,std::remove_reference<T>::type是const char[4],函数参数为const char[4]&,但数组传参时推导规则不匹配,导致报错。
测试验证
#include <iostream> #include <string> void f_take_rv(std::string&& s) { std::cout << "Received rvalue: " << s << std::endl; } template<typename T> void func(T&& arg) { f_take_rv(make_rv<T>(arg)); } int main() { std::string s = "hello"; func(s); // 左值,拷贝生成右值 func(std::string("world")); // 右值,直接转发 func("literal"); // 字符串字面量,正确处理 return 0; }
这段代码可正常编译运行,三种场景均能正确生成右值传入f_take_rv。
内容的提问来源于stack exchange,提问作者hczstev
相关产品推荐
相关产品推荐

