如何从无重叠类型子集的元组构造超集类型元组
实现固定返回类型的MyTuple构造函数(支持子集参数拼接)
问题描述
我有一个本质是std::tuple包装器的类MyTuple(适用于std::tuple的方案同样适用),想要编写构造函数/类似tuple_cat的工具,满足以下要求:
- 可接收任意数量同模板类型的参数
- 每个参数的模板参数是返回类型的子集
- 返回类型固定,未传入的成员自动默认构造
- 禁止参数间出现类型重叠,且保留原类型顺序
直接使用std::tuple_cat的问题在于返回类型由参数决定,无法满足固定返回类型的需求。我给出了示例代码框架(注释中的约束需通过requires子句实现):
template<typename... Ts> // Ts约束为每个类型仅出现一次 struct MyTuple { template<typename... Tuples> // Tuples约束为均为MyTuple的实例化 // 每个Tuples<Us...>约束为Us是Ts的严格子集 // 任意两个Tuples<Us...>约束为Us无重叠 // 使用const&简化代码,暂不考虑std::forward MyTuple(Tuples const&... args) { // 不知如何实现 // 使用初始化列表更好,但更不知如何处理 } std::tuple<Ts...> mTuple; };
使用示例:
// A..E为可默认构造的不同类型 using SuperTuple = MyTuple<A, B, C, D, E>; // 传入A、B、C、E,D默认构造 auto super = SuperTuple{MyTuple<A>{}, MyTuple<E,B>{}, MyTuple<C>{}};
请问这是否可行?
可行性结论与实现方案
完全可行,我们可以借助C++20的**概念(Concepts)**实现编译期约束,结合编译期类型遍历和std::get完成目标tuple的初始化。
1. 基础约束:确保MyTuple模板参数无重复
首先给MyTuple本身添加约束,保证模板参数Ts...中每个类型唯一:
#include <tuple> #include <type_traits> #include <utility> #include <cassert> // 辅助工具:判断类型列表是否存在重复 template <typename T, typename... Rest> constexpr bool has_duplicate = (std::is_same_v<T, Rest> || ...); template <typename... Ts> constexpr bool all_unique = (!has_duplicate<Ts, Ts...> && ...); // 约束MyTuple的模板参数无重复 template <typename... Ts> requires all_unique<Ts...> struct MyTuple { using types = std::tuple<Ts...>; // 暴露内部类型列表 std::tuple<Ts...> mTuple; // 默认构造函数,用于子集MyTuple的创建 MyTuple() = default;
2. 构造函数的编译期约束
接下来给构造函数添加requires约束,满足所有参数要求:
// 辅助概念:判断U是否属于类型列表Ts... template <typename U, typename... TsList> concept is_one_of = (std::is_same_v<U, TsList> || ...); // 辅助概念:判断Us...是TsList...的子集 template <typename... Us, typename... TsList> concept is_subset = (is_one_of<Us, TsList...> && ...); // 辅助常量:判断多个MyTuple的类型无重叠 template <typename... Tuples> constexpr bool no_overlap = true; template <typename... Us, typename... RestTuples> constexpr bool no_overlap<MyTuple<Us...>, RestTuples...> = (!has_duplicate<Us, typename RestTuples::types::value_type...> && ...) && no_overlap<RestTuples...>; // 核心构造函数 template <typename... Tuples> requires (std::is_same_v<Tuples, MyTuple<typename Tuples::types::value_type...>> && ...) // 所有参数都是MyTuple实例 && (is_subset<typename Tuples::types::value_type..., Ts...> && ...) // 每个参数是目标类型的子集 && no_overlap<Tuples...> // 参数间无类型重叠 MyTuple(const Tuples&... args) { // 遍历目标类型列表,逐个初始化成员 auto init_tuple = [&]<std::size_t... Is>(std::index_sequence<Is...>) { mTuple = std::make_tuple( [&]() -> std::tuple_element_t<Is, std::tuple<Ts...>> { std::tuple_element_t<Is, std::tuple<Ts...>> val{}; bool found = false; // 检查每个传入的MyTuple是否包含当前类型 (([&]() { using CurrentType = std::tuple_element_t<Is, std::tuple<Ts...>>; using TupleUs = typename Tuples::types::value_type...; if constexpr (is_one_of<CurrentType, TupleUs...>) { val = std::get<CurrentType>(args.mTuple); found = true; } }(), ...)); return found ? val : CurrentType{}; }()... ); }; init_tuple(std::index_sequence_for<Ts...>{}); } };
3. 完整测试示例
// 定义测试类型 struct A { int a = 0; }; struct B { int b = 1; }; struct C { int c = 2; }; struct D { int d = 3; }; struct E { int e = 4; }; int main() { using SuperTuple = MyTuple<A, B, C, D, E>; // 传入不同子集的MyTuple,D将默认构造 auto super = SuperTuple{ MyTuple<A>{{10}}, MyTuple<E,B>{{20}, {30}}, MyTuple<C>{{40}} }; // 验证初始化结果 assert(std::get<A>(super.mTuple).a == 10); assert(std::get<B>(super.mTuple).b == 30); assert(std::get<C>(super.mTuple).c == 40); assert(std::get<D>(super.mTuple).d == 3); // 默认值 assert(std::get<E>(super.mTuple).e == 20); return 0; }
关键要点说明
- 编译期校验:通过自定义概念和
requires子句,在编译阶段就确保参数符合要求,避免运行时错误。 - 类型遍历与初始化:利用
std::index_sequence遍历目标MyTuple的所有类型,对每个类型检查是否存在于传入的子集中,存在则取值,否则默认构造。 - 无重叠检查:通过递归的
no_overlap常量,确保所有传入的子集类型没有重复,避免冲突。
内容的提问来源于stack exchange,提问作者Demiu
相关产品推荐
相关产品推荐

