You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 17:25:29