如何将C++模板模板参数推导与完美转发结合使用?
问题
我有一个函数,希望通过推导模板模板参数来处理std::optional这类封装其他类型的类型:
template <template <typename> typename M, typename T> void Foo(const M<T>& input); // 推导得出M = std::optional, T = int Foo(std::make_optional(0));
这个实现能正常工作,但它仅接受左值引用作为输入。如果我想让它支持任意类型的引用,并通过完美转发传递给Bar,该怎么做?
如果放弃推导M,实现起来很简单:
template <typename Input> void Foo(Input&& input) { Bar(std::forward<Input>(input)); }
但这样就无法推导模板M了。**有没有办法既能推导M,又能像转发引用一样便捷?**因为Bar需要知道模板M,我不想重复定义4到8次重载函数:
template <template <typename> typename M, typename T> void Foo(M<T>& input) { Bar<M>(input); } template <template <typename> typename M, typename T> void Foo(const M<T>& input) { Bar<M>(input); } template <template <typename> typename M, typename T> void Foo(M<T>&& input) { Bar<M>(std::move(input)); } template <template <typename> typename M, typename T> void Foo(const M<T>&& input) { Bar<M>(std::move(input)); }
解决方案
方案1:类型萃取 + 转发引用(兼容C++11及以上)
这是最通用的方案,通过定义类型萃取器提取封装类型的模板M和内部类型T,同时用转发引用处理所有值类别:
首先定义萃取模板,覆盖M<T>的各种cv和引用变体:
template <typename> struct WrapperTraits; // 基础特化:匹配原始M<T> template <template <typename> typename M, typename T> struct WrapperTraits<M<T>> { template <typename U> using Wrapper = M<U>; // 导出模板M using ValueType = T; }; // 匹配const M<T> template <template <typename> typename M, typename T> struct WrapperTraits<const M<T>> : WrapperTraits<M<T>> {}; // 匹配左值引用 template <template <typename> typename M, typename T> struct WrapperTraits<M<T>&> : WrapperTraits<M<T>> {}; // 匹配const左值引用 template <template <typename> typename M, typename T> struct WrapperTraits<const M<T>&> : WrapperTraits<M<T>> {}; // 匹配右值引用 template <template <typename> typename M, typename T> struct WrapperTraits<M<T>&&> : WrapperTraits<M<T>> {}; // 匹配const右值引用 template <template <typename> typename M, typename T> struct WrapperTraits<const M<T>&&> : WrapperTraits<M<T>> {};
然后只需要写一个转发引用版本的Foo:
template <typename Input> void Foo(Input&& input) { // 移除cv和引用,获取原始M<T>类型 using RawType = std::remove_cvref_t<Input>; using Traits = WrapperTraits<RawType>; // 完美转发参数给Bar,同时传入推导得到的模板M Bar<Traits::template Wrapper>(std::forward<Input>(input)); }
不管传入左值、右值、const或非const的M<T>实例,这个函数都能正确推导M,并保持参数的值类别转发给Bar。
方案2:C++20概念约束(更简洁)
如果使用C++20,可以用概念直接约束输入类型必须是M<T>的形式,结合转发引用实现:
先定义一个概念验证类型是否为单参数模板的实例:
template <typename W, template <typename> typename M> concept IsWrapper = requires { // 验证W可以隐式转换为M<T>(T为W的内部值类型) []<typename T>(M<T>) {}(std::declval<W>()); };
然后利用概念和转发引用实现Foo:
template <typename Input> requires IsWrapper<std::remove_cvref_t<Input>, auto> void Foo(Input&& input) { using RawType = std::remove_cvref_t<Input>; // 用lambda推导模板M和内部类型T auto get_wrapper = []<template <typename> typename M, typename T>(M<T>) { return std::type_identity<M>{}; }; using MType = decltype(get_wrapper(std::declval<RawType>()))::type; Bar<MType>(std::forward<Input>(input)); }
这个方案无需额外的萃取模板,利用C++20的模板lambda和概念实现更简洁的推导。
方案3:简化重载写法(兼容C++11)
如果不想写类型萃取,也可以通过3个重载覆盖绝大多数场景,避免写4个重载:
// 处理非const左值 template <template <typename> typename M, typename T> void Foo(M<T>& input) { Bar<M>(std::forward<decltype(input)>(input)); } // 处理const左值 template <template <typename> typename M, typename T> void Foo(const M<T>& input) { Bar<M>(std::forward<decltype(input)>(input)); } // 处理所有右值(包括const右值,实际场景中const右值极少出现) template <template <typename> typename M, typename T> void Foo(M<T>&& input) { Bar<M>(std::forward<decltype(input)>(input)); }
内容的提问来源于stack exchange,提问作者jacobsa
相关产品推荐
相关产品推荐

