如何实现可直接传入类型-整数对的可变参数C++模板?
实现简化的「类型-大小对」可变参数模板
问题描述
我正在编写一个接受可变参数「类型-大小对」的模板,每个对由类型和大小组成。目前已经实现了一个可用版本:
#include <cstddef> #include <iostream> template <typename T, size_t N> class DeferPair { using Type = T; static constexpr size_t kSize = N; public: void defer(const T& e) { std::cout << __PRETTY_FUNCTION__ << std::endl; //Defer this specific type into a fixed-length/statically-allocated queue //Discard oldest if the queue is already full } }; template <typename... Pairs> class Defer; template <typename Pair, typename... RemainingPairs> class Defer<Pair, RemainingPairs...> : public Pair, public Defer<RemainingPairs...> { public: using Pair::defer; using Defer<RemainingPairs...>::defer; }; template <typename Pair> class Defer<Pair> : public Pair { public: using Pair::defer; }; struct A {}; struct B {}; struct C {}; int main() { Defer<DeferPair<A, 1>, DeferPair<B, 2>, DeferPair<C, 3>> d; d.defer(A{}); d.defer(B{}); d.defer(C{}); return 0; }
我希望简化使用语法,将当前写法:
Defer<DeferPair<A, 1>, DeferPair<B, 2>, DeferPair<C, 3>> d;
改为更简洁的形式:
Defer<A, 1, B, 2, C, 3> d;
想知道这是否可以实现,以及这种写法的可读性如何,有没有更优的实现方案。
一、可以实现,以下是具体方案
我们可以通过模板元编程对参数包进行分组处理,自动将连续的「类型+非类型参数」组装成DeferPair,直接适配简化后的语法。
直接修改Defer模板的实现(推荐)
这个方案无需额外辅助类,直接让Defer处理成对的参数,通过静态断言保证参数数量为偶数:
#include <cstddef> #include <iostream> template <typename T, size_t N> class DeferPair { using Type = T; static constexpr size_t kSize = N; public: void defer(const T& e) { std::cout << __PRETTY_FUNCTION__ << std::endl; // 将特定类型元素存入固定长度静态队列,满时丢弃最旧元素 } }; // 辅助实现类,处理递归参数包 template <typename...> class DeferImpl; // 递归终止:空参数包 template <> class DeferImpl<> {}; // 递归处理每一对(类型+大小)参数 template <typename T, size_t N, typename... Rest> class DeferImpl<T, N, Rest...> : public DeferPair<T, N>, public DeferImpl<Rest...> { public: // 引入所有基类的defer函数 using DeferPair<T, N>::defer; using DeferImpl<Rest...>::defer; }; // 对外暴露的简化版Defer模板 template <typename... Args> class Defer : public DeferImpl<Args...> { // 静态断言:参数必须成对出现 static_assert(sizeof...(Args) % 2 == 0, "Defer template arguments must be in (Type, Size) pairs"); }; struct A {}; struct B {}; struct C {}; int main() { // 使用简化语法 Defer<A, 1, B, 2, C, 3> d; d.defer(A{}); d.defer(B{}); d.defer(C{}); return 0; }
编译运行后,输出结果和原版本完全一致,但使用语法更简洁。
二、可读性分析
- 简化写法的优势:
Defer<A,1,B,2,C,3>减少了重复的DeferPair声明,对于熟悉模板参数规则的开发者来说,可读性更高,能快速对应每个类型的大小配置。 - 潜在的困惑:对模板元编程不熟悉的开发者可能会混淆参数类型(比如分不清哪个是类型、哪个是大小),但通过命名规范(如大小用数字字面量、类型用自定义结构体/类)可以有效缓解这个问题。
- 原写法的价值:原写法
Defer<DeferPair<A,1>,...>虽然冗长,但结构更明确,每个DeferPair清晰标记一个「类型-大小对」,对新手更友好。
三、更优实现方案
1. C++17+ 折叠表达式优化
如果使用C++17及以上,可以利用折叠表达式简化defer函数的引入,避免递归的using声明:
#include <cstddef> #include <iostream> template <typename T, size_t N> class DeferPair { using Type = T; static constexpr size_t kSize = N; public: void defer(const T& e) { std::cout << __PRETTY_FUNCTION__ << std::endl; } }; // 用折叠表达式一次性引入所有基类的defer template <typename... Pairs> class DeferImpl : public Pairs... { public: using Pairs::defer...; }; // 辅助模板:将参数包组装成DeferPair序列 template <typename... Args> struct DeferHelper; template <typename T, size_t N, typename... Rest> struct DeferHelper<T, N, Rest...> { using type = DeferImpl<DeferPair<T, N>, typename DeferHelper<Rest...>::type>; }; template <> struct DeferHelper<> { using type = DeferImpl<>; }; // 对外暴露的Defer模板 template <typename... Args> class Defer : public DeferHelper<Args...>::type { static_assert(sizeof...(Args) % 2 == 0, "Defer requires (Type, Size) pairs"); }; struct A {}; struct B {}; struct C {}; int main() { Defer<A,1,B,2,C,3> d; d.defer(A{}); d.defer(B{}); d.defer(C{}); return 0; }
2. 基于Tuple的无继承实现(C++20+)
如果不想使用多继承,可以用std::tuple存储所有DeferPair实例,通过C++20的auto参数和constexpr递归分发调用:
#include <cstddef> #include <iostream> #include <tuple> template <typename T, size_t N> class DeferPair { public: using Type = T; static constexpr size_t kSize = N; void defer(const T& e) { std::cout << __PRETTY_FUNCTION__ << std::endl; } }; // 辅助模板:生成DeferPair的tuple类型 template <typename... Args> struct DeferTupleBuilder; template <typename T, size_t N, typename... Rest> struct DeferTupleBuilder<T, N, Rest...> { using type = decltype(std::tuple_cat( std::declval<std::tuple<DeferPair<T, N>>>(), std::declval<typename DeferTupleBuilder<Rest...>::type>() )); }; template <> struct DeferTupleBuilder<> { using type = std::tuple<>; }; template <typename... Args> class Defer { static_assert(sizeof...(Args) % 2 == 0, "Defer requires (Type, Size) pairs"); using TupleType = typename DeferTupleBuilder<Args...>::type; TupleType pairs_; // 递归分发调用到对应的DeferPair template <size_t I = 0> void dispatch(const auto& e) { if constexpr (I < std::tuple_size_v<TupleType>) { using Pair = std::tuple_element_t<I, TupleType>; if constexpr (std::is_same_v<std::decay_t<decltype(e)>, typename Pair::Type>) { std::get<I>(pairs_).defer(e); } else { dispatch<I+1>(e); } } } public: void defer(const auto& e) { dispatch(e); } }; struct A {}; struct B {}; struct C {}; int main() { Defer<A,1,B,2,C,3> d; d.defer(A{}); d.defer(B{}); d.defer(C{}); return 0; }
这个方案避免了多继承,代码结构更清晰,也更易于扩展新功能。
内容的提问来源于stack exchange,提问作者Patrick Wright
相关产品推荐
相关产品推荐

