如何用C++20可变参数模板将区间拆分为任意数量子区间
问题描述
我需要将自定义的interval类拆分为任意数量的子区间,子区间数量由传入参数决定。目前已有拆分2或3个子区间的特例实现,现寻求兼容C++20的通用实现,要求高效、无额外中间vector对象。
现有interval类实现:
class interval { public: interval(std::string_view name, float start, float end) : name(name) , start(start) , end(end) { if (start >= end) throw std::invalid_argument("`start` must be smaller than `end`!"); } interval split_off_chunk(std::string_view name, float relStart, float relEnd) const { if (!((0 <= relStart) && (relStart < relEnd) && (relEnd <= 1))) throw std::invalid_argument( "`relStart`, `relEnd` must be relative values, e.g. `0<=relStart < " "relEnd <= 1`!"); const float len = end - start; const float childStart = (relStart == 0) ? start : start + relStart * len; const float childEnd = (relEnd == 1) ? end : start + relEnd * len; return { name, childStart, childEnd }; } std::array<interval, 2> split(std::string_view name0, float relSplit01, std::string_view name1) const { if (!((0 < relSplit01) && (relSplit01 < 1))) throw std::invalid_argument( "`relSplit01` must be a relative value, e.g. `0 < relSplit01 < " "1`!"); return { split_off_chunk(name0, 0, relSplit01), split_off_chunk(name1, relSplit01, 1) }; } std::array<interval, 3> split(std::string_view name0, float relSplit01, std::string_view name1, float relSplit12, std::string_view name2) const { if (!((0 < relSplit01) && (relSplit01 < relSplit12) && (relSplit12 < 1))) throw std::invalid_argument( "`relSplit` must be relative values, e.g. `0 < relSplit01 < " "relSplit12 < 1`!"); return { split_off_chunk(name0, 0, relSplit01), split_off_chunk(name1, relSplit01, relSplit12), split_off_chunk(name2, relSplit12, 1) }; } void print() const { std::print("name: {}. [{}, {}]\n", name, start, end); } private: std::string name; float start; float end; };
我尝试编写了如下模板代码,但有几个问题待解决:
template<class... Args> consteval bool CheckArgs() { if (sizeof...(Args) % 2 != 1) return false; if (sizeof...(Args) < 3) return false; // 如何检查参数是交替的string_view和float? } template<class... Args> consteval size_t GetCount() { return (sizeof...(Args)+1)/2; } template<class... Args> requires (CheckArgs<Args>()) std::array<interval, GetCount<Args...>()> split(Args... args) { // 如何提取名称和分割点float? // 如何创建并初始化数组? }
额外需求
- 能否添加带默认值的尾部布尔参数?示例参考:
class interval { public: interval(std::string_view name, float start, float end, bool flag = true); interval split_off_chunk(std::string_view name, float relStart, float relEnd, bool flag = true) const { // ... return {name, childStart, childEnd, flag}; } std::array<interval, 2> split(std::string_view name0, float relSplit01, std::string_view name1, bool flag = true) const { // ... return {split_off_chunk(name0, 0, relSplit01, flag), split_off_chunk(name1, relSplit01, 1, flag)}; } std::array<interval, 3> split(std::string_view name0, float relSplit01, std::string_view name1, float relSplit12, std::string_view name2, bool flag = true) const; // ... bool flag; };
- 当前子区间始终以0为起始、1为结束相对值,能否支持自定义首尾值?例如检查可变参数首个类型是否为float,若是则替代默认0;同理末尾若为float则替代默认1。
解决方案
1. 用C++20概念实现参数类型检查
替代原有的CheckArgs,用编译期概念严格校验参数序列的合法性:
#include <concepts> #include <string_view> #include <tuple> // 基础参数序列校验:名称、分割点、名称、分割点...名称 template<typename First, typename... Rest> concept ValidSplitArgs = std::convertible_to<First, std::string_view> && ([]<typename... Pairs>(std::type_identity<Pairs>...) { return (... && (std::tuple_size_v<Pairs> == 2 && std::convertible_to<std::tuple_element_t<0, Pairs>, float> && std::convertible_to<std::tuple_element_t<1, Pairs>, std::string_view>)); })(std::type_identity<std::pair<Rest..., void>>{}...); // 带自定义起始值的参数序列:float开头,后续为基础合法序列 template<typename First, typename... Rest> concept SplitWithCustomStart = std::convertible_to<First, float> && ValidSplitArgs<Rest...>; // 带自定义结束值的参数序列:基础合法序列末尾追加float template<typename... Args> concept SplitWithCustomEnd = ValidSplitArgs<Args...> && std::convertible_to<std::tuple_element_t<sizeof...(Args)-1, std::tuple<Args...>>, float>; // 同时带自定义首尾值的参数序列:float开头,基础合法序列,float结尾 template<typename First, typename... Rest> concept SplitWithCustomStartEnd = std::convertible_to<First, float> && []<typename... Pairs, typename Last>(std::type_identity<Pairs>..., std::type_identity<Last>) { return ValidSplitArgs<Pairs...> && std::convertible_to<Last, float>; }(std::type_identity<Rest...>{});
2. 编译期参数拆分与数组生成
用std::index_sequence和元组操作,在编译期拆分参数并生成目标std::array:
#include <array> #include <utility> namespace detail { // 核心生成逻辑:从参数元组生成子区间数组 template<size_t... Idxs, typename... Args> std::array<interval, sizeof...(Idxs)+1> make_split_array(const interval& self, std::index_sequence<Idxs...>, const std::tuple<Args...>& args, float start_rel, float end_rel, bool flag) { const auto names = std::make_tuple(std::get<2*Idxs>(args)...); const auto splits = std::make_tuple(std::get<2*Idxs+1>(args)...); return { self.split_off_chunk(std::get<0>(names), start_rel, std::get<0>(splits), flag), (self.split_off_chunk(std::get<Idxs+1>(names), std::get<Idxs>(splits), std::get<Idxs+1>(splits), flag))..., self.split_off_chunk(std::get<sizeof...(Idxs)>(names), std::get<sizeof...(Idxs)-1>(splits), end_rel, flag) }; } // 自定义起始值的适配 template<size_t... Idxs, typename... Args> std::array<interval, sizeof...(Idxs)+1> make_split_with_start(const interval& self, std::index_sequence<Idxs...>, float custom_start, const std::tuple<Args...>& args, float end_rel, bool flag) { return make_split_array(self, std::index_sequence<Idxs...>{}, args, custom_start, end_rel, flag); } // 自定义结束值的适配 template<size_t... Idxs, typename... Args> std::array<interval, sizeof...(Idxs)+1> make_split_with_end(const interval& self, std::index_sequence<Idxs...>, const std::tuple<Args...>& args, float custom_end, bool flag) { return make_split_array(self, std::index_sequence<Idxs...>{}, args, 0.f, custom_end, flag); } // 自定义首尾值的适配 template<size_t... Idxs, typename... Args> std::array<interval, sizeof...(Idxs)+1> make_split_with_both(const interval& self, std::index_sequence<Idxs...>, float custom_start, const std::tuple<Args...>& args, float custom_end, bool flag) { return make_split_array(self, std::index_sequence<Idxs...>{}, args, custom_start, custom_end, flag); } }
3. 更新interval类实现通用split函数
整合上述逻辑,同时支持所有额外需求:
class interval { public: interval(std::string_view name, float start, float end, bool flag = true) : name(name) , start(start) , end(end) , flag(flag) { if (start >= end) throw std::invalid_argument("`start` must be smaller than `end`!"); } interval split_off_chunk(std::string_view name, float relStart, float relEnd, bool flag = true) const { if (!((0 <= relStart) && (relStart < relEnd) && (relEnd <= 1))) throw std::invalid_argument( "`relStart`, `relEnd` must be relative values, e.g. `0<=relStart < " "relEnd <= 1`!"); const float len = end - start; const float childStart = start + relStart * len; const float childEnd = start + relEnd * len; return { name, childStart, childEnd, flag }; } // 基础通用拆分:名称、分割点、...名称,可选尾部flag template<ValidSplitArgs... Args> auto split(Args... args, bool flag = true) const { constexpr size_t split_count = (sizeof...(Args)-1)/2; auto args_tuple = std::make_tuple(args...); return detail::make_split_array(*this, std::make_index_sequence<split_count>{}, args_tuple, 0.f, 1.f, flag); } // 自定义起始值的拆分:float、名称、分割点、...名称,可选尾部flag template<SplitWithCustomStart... Args> auto split(Args... args, bool flag = true) const { auto args_tuple = std::make_tuple(args...); float custom_start = std::get<0>(args_tuple); auto rest_tuple = std::apply([](auto first, auto... rest) { return std::make_tuple(rest...); }, args_tuple); constexpr size_t split_count = (sizeof...(Args)-2)/2; return detail::make_split_with_start(*this, std::make_index_sequence<split_count>{}, custom_start, rest_tuple, 1.f, flag); } // 自定义结束值的拆分:名称、分割点、...名称、float,可选尾部flag template<SplitWithCustomEnd... Args> auto split(Args... args, bool flag = true) const { auto args_tuple = std::make_tuple(args...); constexpr size_t last_idx = sizeof...(Args)-1; float custom_end = std::get<last_idx>(args_tuple); auto rest_tuple = std::apply([](auto... elems) { return std::make_tuple(std::get<Idxs>(std::make_tuple(elems...))...); }<size_t... Idxs>(std::make_index_sequence<last_idx>{}), args_tuple); constexpr size_t split_count = (sizeof...(Args)-2)/2; return detail::make_split_with_end(*this, std::make_index_sequence<split_count>{}, rest_tuple, custom_end, flag); } // 自定义首尾值的拆分:float、名称、分割点、...名称、float,可选尾部flag template<SplitWithCustomStartEnd... Args> auto split(Args... args, bool flag = true) const { auto args_tuple = std::make_tuple(args...); float custom_start = std::get<0>(args_tuple); constexpr size_t last_idx = sizeof...(Args)-1; float custom_end = std::get<last_idx>(args_tuple); auto rest_tuple = std::apply([](auto first, auto... elems, auto last) { return std::make_tuple(elems...); }, args_tuple); constexpr size_t split_count = (sizeof...(Args)-3)/2; return detail::make_split_with_both(*this, std::make_index_sequence<split_count>{}, custom_start, rest_tuple, custom_end, flag); } // 保留原有2、3拆分的重载(可选,通用版本已覆盖) std::array<interval, 2> split(std::string_view name0, float relSplit01, std::string_view name1, bool flag = true) const { if (!((0 < relSplit01) && (relSplit01 < 1))) throw std::invalid_argument("`relSplit01` must be 0 < value < 1!"); return { split_off_chunk(name0, 0, relSplit01, flag), split_off_chunk(name1, relSplit01, 1, flag) }; } std::array<interval, 3> split(std::string_view name0, float relSplit01, std::string_view name1, float relSplit12, std::string_view name2, bool flag = true) const { if (!((0 < relSplit01) && (relSplit01 < relSplit12) && (relSplit12 < 1))) throw std::invalid_argument("`relSplit` values must be 0 < val1 < val2 < 1!"); return { split_off_chunk(name0, 0, relSplit01, flag), split_off_chunk(name1, relSplit01, relSplit12, flag), split_off_chunk(name2, relSplit12, 1, flag) }; } void print() const { std::print("name: {}. [{}, {}], flag: {}\n", name, start, end, flag); } private: std::string name; float start; float end; bool flag; };
4. 使用示例
#include <iostream> int main() { interval original("original", 0.f, 100.f); // 拆分4个子区间,默认flag=true auto split4 = original.split("part1", 0.25f, "part2", 0.5f, "part3", 0.75f, "part4"); for (const auto& i : split4) i.print(); // 自定义起始值,flag=false auto split_custom_start = original.split(0.1f, "start", 0.3f, "mid", 0.6f, "end", false); for (const auto& i : split_custom_start) i.print(); // 自定义结束值 auto split_custom_end = original.split("first", 0.2f, "second", 0.4f, "third", 0.8f); for (const auto& i : split_custom_end) i.print(); // 同时自定义首尾值 auto split_custom_both = original.split(0.1f, "a", 0.3f, "b", 0.6f, "c", 0.9f); for (const auto& i : split_custom_both) i.print(); return 0; }
关键特性说明
- 所有逻辑均为编译期计算,直接生成
std::array,无额外内存开销。 - 用C++20概念在编译期拦截非法参数,避免运行时错误。
- 尾部布尔参数支持默认值,兼容原有调用方式。
- 覆盖四种参数模式:默认首尾、自定义起始、自定义结束、自定义首尾,满足所有需求场景
相关产品推荐
相关产品推荐

