如何在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
相关产品推荐
相关产品推荐

