折叠表达式替换特定类型转发其余类型时如何正确特化std::forward
我尝试在折叠表达式中替换某一特定类型,其余所有类型直接执行完美转发,但多次尝试均未成功。
以下是我模拟实现的std::forward,实际复制了GCC中std::forward的实现,仅添加了输出语句用于观察执行流程:
namespace test { template<typename T> T&& fw(typename std::remove_reference<T>::type& t) noexcept { std::cout << "standard" << std::endl; return static_cast<T&&>(t); } template<typename T> T&& fw(typename std::remove_reference<T>::type&& t) noexcept { std::cout << "standard (r-value)" << std::endl; static_assert ( !std::is_lvalue_reference<T>::value, "template argument substituting T is an lvalue reference type" ); return static_cast<T&&>(t); } }
由于std::forward要求显式指定模板参数,我曾尝试新增一组模板重载处理目标类型,但这些重载不会被重载决议选中,且就算能被匹配也会引发二义性调用问题。
之后我尝试特化转发函数模板(我清楚如果直接特化std命名空间下的std::forward,在头文件中使用时会在非预期场景触发替换逻辑),以特化处理std::string类型为例。纯粹出于技术好奇心(我后续发现实际不需要使用可变参模板,直接在函数内使用constexpr if即可实现需求,因此不存在XY问题),我想知道正确的特化方式应该是怎样的?
我曾尝试为所有可能的引用类型提供对应特化(原本计划通过去掉其中一个特化解决二义性问题),但编译直接失败:
namespace test { template<> std::string const& fw<std::string const>(std::string const& t) noexcept { std::cout << "specialised const" << std::endl; return t; } #if 0 template<> std::string& fw<std::string>(std::string& t) noexcept { std::cout << "specialised non-const" << std::endl; return t; } #endif template<> std::string && fw<std::string>(std::string&& t) noexcept { std::cout << "specialised r-value" << std::endl; return std::move(t); } }
使用如下测试代码验证:
int main() { test::fw<int>(7); int n; test::fw<int>(n); test::fw<std::string>(std::string("alda")); std::string s("daal"); test::fw<std::string>(s); return 0; }
预期运行输出为:
standard standard specialised r-value specialised non-const
哪怕仅提供左值引用的单个特化版本也可以满足需求。
但实际编译时报错:
<source>:27:20: error: template-id 'fw<const std::string>' for 'const std::string& test::fw(const std::string&)' does not match any template declaration 27 | std::string const& fw<std::string const>(std::string const& t) noexcept | ^~~~~~~~~~~~~~~~~~~~~ <source>:15:5: note: candidates are: 'template<class T> T&& test::fw(typename std::remove_reference<_Tp>::type&&)' 15 | T&& fw(typename std::remove_reference<T>::type&& t) noexcept | ^~ <source>:8:5: note: 'template<class T> T&& test::fw(typename std::remove_reference<_Tp>::type&)' 8 | T&& fw(typename std::remove_reference<T>::type& t) noexcept | ^~
报错提示模板声明不匹配,看起来仅左值引用的特化存在匹配问题,但我无法定位原因——明明通用模板中存在typename std::remove_reference<T>::type&参数的重载版本。
如果可以正确完成特化,我就可以在自定义命名空间中重实现std::forward,添加对应特化后直接使用;但我最终想知道的是:如何不用从零重写std::forward就能实现该需求?
问题解答
特化编译失败的根本原因
函数模板特化要求返回值类型、参数类型必须和主模板实例化后的签名严格匹配。你写的左值版本主模板返回值是T&&,当T = const std::string时,返回值类型是const std::string&&,而你写的特化返回值是const std::string&,签名不匹配自然编译报错。右值版本的特化同理,当T=std::string时主模板参数类型是std::string&&、返回值也是std::string&&,这部分签名是匹配的,所以右值特化本身不会触发编译错误。
不推荐用函数模板特化实现这个需求:C++中函数模板特化不参与重载决议,一旦后续新增重载很容易出现特化绑定到不符合预期的主模板的问题,维护成本极高。无需重写
std::forward的实现方案
直接写自定义转发包装层即可,用if constexpr判断类型,命中特定类型时走自定义逻辑,其余类型直接调用标准库std::forward完成转发,完全不需要自己重写整个forward实现,编译期判断无运行时开销,也不会出现重载二义性、特化匹配错误的问题:
#include <utility> #include <string> #include <iostream> #include <type_traits> template <typename T> decltype(auto) my_fw(std::remove_reference_t<T>& t) noexcept { if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::string>) { // 自定义std::string类型的处理逻辑 if constexpr (std::is_lvalue_reference_v<T>) { std::cout << "custom string lvalue forward\n"; } else { std::cout << "custom string rvalue forward\n"; } // 此处可添加替换逻辑,比如返回修改后的对象、做特殊类型转换等 return static_cast<T&&>(t); } else { // 其余类型直接走标准完美转发 std::cout << "standard forward\n"; return std::forward<T>(t); } } template <typename T> decltype(auto) my_fw(std::remove_reference_t<T>&& t) noexcept { static_assert(!std::is_lvalue_reference_v<T>, "cannot forward rvalue as lvalue"); if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::string>) { std::cout << "custom string rvalue forward\n"; return static_cast<T&&>(t); } else { std::cout << "standard (r-value) forward\n"; return std::forward<T>(t); } }
这个实现可以直接在折叠表达式中使用,行为和预期完全一致。
内容的提问来源于stack exchange,提问作者Aconcagua

