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

如何在C++可变参数模板类中定义内部类型以暴露参数包?

问题描述

我有一个使用可变参数模板的结构体:

template <typename... Args>
struct Outer
{
  // using Inner = something that captures ...Args ???;
}

我该如何定义Inner,才能在后续模板代码中这样使用:

// ... 一些接受其他模板参数的已有函数
template <typename... Args2>
foo(Args2 ...)
{
 // ...
};
// ... 定义一些特化的Outer示例(不限于这些)
using Specific1 = Outer<int, double, bool>;
using Specific2 = Outer<std::string>;
// ...
foo(Specific1::Inner... args)
// ...
foo(Specific2::Inner... args)

我主要关注C17版本,但也想了解其他C版本的实现方式,理想情况下不想借助std::tuple。

以下是最小可复现代码:

template <typename... Args>
struct Outer
{
    using Cb = std::function<void(Args...)>;

    //using Inner = Args; // 报错:"parameter pack must be expanded in this context"
    //using Inner = Args...; // 报错:"parameter pack cannot be expanded in this context"

    template<typename CbIn>
    void store(CbIn&& cb)
    {
        mCb = std::forward<CbIn>(cb);
    }

    void call(Args... args) noexcept
    {
        mCb(args...);
    }

    // 此处cb接受Args...并返回OtherOuter*(特化后的参数不同)
    template<typename OtherOuter, typename CbIn>
    void foo(CbIn&& cb)
    {
        store([cb{ std::forward<CbIn>(cb)}](Args... args)
        {
            OtherOuter * other = cb(std::forward<Args>(args)...);
            other->store([](/*OtherOuter::Inner*/ ... otherArgs) // 这里如果不用OtherOuter::Inner该填什么?
            {
                // 处理otherArgs
                ([&]
                {
                    std::cout << "second " << otherArgs << std::endl;
                } (), ...);
            });
            std::cout << "first " << other->mFlag << std::endl;
        });
    }

    Cb mCb;
    bool mFlag = false;
};

使用示例:

using OuterIntBool = Outer<int, bool>;
using OuterString = Outer<std::string>;
// 可正常运行
{
    OuterIntBool outerIntBool;
    outerIntBool.store([](int i, bool b)
    {
        bool isValid = i > 0 && b;
        assert(isValid);
    });
    outerIntBool.call(1, true);
}
// 无法运行
{
    OuterIntBool outerIntBool;
    OuterString otherString;
    outerIntBool.foo<OuterString>([&otherString](int/* i*/, bool b)
    {
        otherString.mFlag = b;
        return &otherString;
    });
    outerIntBool.call(1, true);
    otherString.call("bar");
}

解决方案

C++17及以上版本

你没法直接把参数包定义成单个using别名,但可以通过模板别名来实现参数包的暴露与展开,完全不需要std::tuple:

实现方式

给Outer添加一个模板别名,专门用于传递参数包:

template <typename... Args>
struct Outer
{
    using Cb = std::function<void(Args...)>;
    
    // 模板别名,用于展开当前Outer的参数包
    template<typename...>
    using InnerPack = Args...;

    // ... 原有代码不变 ...

    template<typename OtherOuter, typename CbIn>
    void foo(CbIn&& cb)
    {
        store([cb{ std::forward<CbIn>(cb)}](Args... args)
        {
            OtherOuter * other = cb(std::forward<Args>(args)...);
            // 展开OtherOuter的参数包
            other->store([](typename OtherOuter::template InnerPack<>... otherArgs)
            {
                ([&]
                {
                    std::cout << "second " << otherArgs << std::endl;
                } (), ...);
            });
            std::cout << "first " << other->mFlag << std::endl;
        });
    }

    // ... 原有成员 ...
};

这里的InnerPack<>...会直接展开OtherOuter的Args参数包,完美替代你想要的OtherOuter::Inner...写法。

旧C版本(C11/C++14)

如果要兼容更早的版本,没法用模板别名的展开技巧,可以借助auto参数推导+类型校验的方式:

template <typename... Args>
struct Outer
{
    using Cb = std::function<void(Args...)>;
    
    // ... 原有代码不变 ...

    template<typename OtherOuter, typename CbIn>
    void foo(CbIn&& cb)
    {
        store([cb{ std::forward<CbIn>(cb)}](Args... args)
        {
            OtherOuter * other = cb(std::forward<Args>(args)...);
            // 用auto推导参数,再校验类型是否匹配
            other->store([](auto... otherArgs)
            {
                static_assert(std::is_same<std::tuple<decltype(otherArgs)...>, std::tuple<typename OtherOuter::Args...>>::value, "参数类型不匹配");
                ([&]
                {
                    std::cout << "second " << otherArgs << std::endl;
                } (), ...);
            });
            std::cout << "first " << other->mFlag << std::endl;
        });
    }

    // 额外添加参数包的tuple别名用于校验(如果允许用tuple的话)
    using ArgsTuple = std::tuple<Args...>;
};

这种方式虽然需要用到std::tuple做类型校验,但核心逻辑不需要依赖tuple来传递参数。


最终可运行代码

将C++17的方案应用到示例中,完整代码如下:

#include <functional>
#include <iostream>
#include <cassert>
#include <string>

template <typename... Args>
struct Outer
{
    using Cb = std::function<void(Args...)>;
    
    template<typename...>
    using InnerPack = Args...;

    template<typename CbIn>
    void store(CbIn&& cb)
    {
        mCb = std::forward<CbIn>(cb);
    }

    void call(Args... args) noexcept
    {
        mCb(args...);
    }

    template<typename OtherOuter, typename CbIn>
    void foo(CbIn&& cb)
    {
        store([cb{ std::forward<CbIn>(cb)}](Args... args)
        {
            OtherOuter * other = cb(std::forward<Args>(args)...);
            other->store([](typename OtherOuter::template InnerPack<>... otherArgs)
            {
                ([&]
                {
                    std::cout << "second " << otherArgs << std::endl;
                } (), ...);
            });
            std::cout << "first " << other->mFlag << std::endl;
        });
    }

    Cb mCb;
    bool mFlag = false;
};

int main()
{
    using OuterIntBool = Outer<int, bool>;
    using OuterString = Outer<std::string>;
    
    {
        OuterIntBool outerIntBool;
        outerIntBool.store([](int i, bool b)
        {
            bool isValid = i > 0 && b;
            assert(isValid);
        });
        outerIntBool.call(1, true);
    }
    
    {
        OuterIntBool outerIntBool;
        OuterString otherString;
        outerIntBool.foo<OuterString>([&otherString](int/* i*/, bool b)
        {
            otherString.mFlag = b;
            return &otherString;
        });
        outerIntBool.call(1, true);
        otherString.call("bar");
    }

    return 0;
}

运行后会输出:

first 1
second bar

内容的提问来源于stack exchange,提问作者Juan Gonzalez Burgos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 23:25:54