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

如何用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?
    // 如何创建并初始化数组?
}

额外需求

  1. 能否添加带默认值的尾部布尔参数?示例参考:
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;
};
  1. 当前子区间始终以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概念在编译期拦截非法参数,避免运行时错误。
  • 尾部布尔参数支持默认值,兼容原有调用方式。
  • 覆盖四种参数模式:默认首尾、自定义起始、自定义结束、自定义首尾,满足所有需求场景
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:27:22