显式指定模板参数时如何实现C++完美转发?
实现兼容显式模板参数的完美转发函数
问题描述
先看这个简单的模板函数:
template <typename U> auto mkVector(U&& x0) { return std::vector<std::decay_t<U>>{std::forward<U>(x0)}; }
它支持两种调用方式:依赖模板推导,或者显式指定模板类型,结合左/右值参数,共有四种场景:
const string lvalue("hello"); // 类型推导场景 auto v1 = mkVector(lvalue); // 传入左值,正常编译 auto v2 = mkVector(string{}); // 传入右值,正常编译 // 显式指定类型场景 auto v3 = mkVector<string>(lvalue); // 传入左值,编译失败 auto v4 = mkVector<string>(""); // 传入右值,正常编译
其中v3编译失败的原因很明确:当显式指定U=string时,U&&是右值引用string&&,无法匹配左值lvalue。
现在需要实现一个mkVector,让它在所有四种场景下都能正确工作,同时支持完美转发。目前能想到的是写两个重载,但这种方案扩展性极差——如果函数有N个参数,就需要写2^N个重载:
template <typename U, std::enable_if_t<std::is_rvalue_reference_v<U>, bool> = true> auto mkVector(U&& x0) { return std::vector<U>{std::move(x0)}; } template <typename U> auto mkVector(const U& x0) { return std::vector<U>{x0}; }
最优解决方案
我们可以通过拆分模板参数的方式,把vector的元素类型和参数的引用类型分开处理,避免引用折叠带来的冲突,实现一个无需大量重载的版本:
#include <vector> #include <type_traits> #include <string> template <typename T> struct mkVectorHelper { template <typename U> static auto create(U&& x0) { return std::vector<T>{std::forward<U>(x0)}; } }; template <typename T = void, typename U> auto mkVector(U&& x0) { if constexpr (std::is_same_v<T, void>) { // 隐式推导时,用参数衰减后的类型作为vector元素类型 return mkVectorHelper<std::decay_t<U>>::create(std::forward<U>(x0)); } else { // 显式指定类型时,直接用T作为元素类型,完美转发参数 return mkVectorHelper<T>::create(std::forward<U>(x0)); } }
方案解析
- 隐式推导逻辑:当不指定模板参数时,
T默认是void,此时通过std::decay_t<U>获取参数的衰减类型(比如左值const string会衰减为string),以此作为vector的元素类型,同时用std::forward<U>完美转发参数的左右值属性。 - 显式指定类型逻辑:当指定
T(比如mkVector<string>(lvalue)),直接用T作为vector的元素类型。此时参数的模板U会根据传入的实参自动推导:- 传入左值
lvalue时,U推导为const string&,U&&通过引用折叠变为const string&,匹配左值; - 传入右值时,
U推导为string,U&&就是string&&,匹配右值。
- 传入左值
- 扩展性:如果需要支持N个参数,只需要修改
mkVector和create的参数列表即可,完全不用写多个重载,解决了2^N重载的痛点。
验证所有场景
把四种调用场景代入测试,全部可以正常编译:
const string lvalue("hello"); // 隐式推导 auto v1 = mkVector(lvalue); // vector<string>,拷贝左值 auto v2 = mkVector(string{}); // vector<string>,移动临时对象 // 显式指定类型 auto v3 = mkVector<string>(lvalue); // vector<string>,拷贝左值 auto v4 = mkVector<string>(""); // vector<string>,移动临时字符串
所有场景都能正确完美转发参数,符合预期。
内容的提问来源于stack exchange,提问作者Fabio
相关产品推荐
相关产品推荐

