如何实现 constexpr N-way set_union去重合并

问题描述

我有一堆结构体,其中包含不同数量std::array 的预排序 size_t。作为一个玩具示例,假设我们有以下三个结构:

struct F_A { static constexpr std::array<size_t,4> bounds = { 0,100,200,300 }; };
struct F_B { static constexpr std::array<size_t,5> bounds = { 0,125,250,300,500 }; };
struct F_C { static constexpr std::array<size_t,4> bounds = { 100,301 }; };

目标是在编译时执行相当于 N-way std::set_union 的;例如,鉴于上述结构,我希望能够编写

constexpr auto bounds = merge_bounds<F_A,F_B,F_C>();

并以 bounds 作为包含值 constexpr std::array<size_t,8>0,301,500 结束。

合并来自结构的bounds数组非常容易;但是,对于如何最好地将其概括为使用可变参数模板和参数包,我有点不知所措。为了让成对的版本工作,我首先“模拟”合并以确定合并数组在实际进行合并之前的长度,但是当与参数包结合时,这种方法变得非常麻烦。 (我怀疑即使我的配对代码也远不如我对某些相关语言功能有更好的处理能力...)

这是一个 MWE,展示了我的配对功能代码

#include <cstdlib>
#include <iostream>
#include <array>

struct F_A { static constexpr std::array<size_t,301 }; };

template <typename F0,typename F1>
inline static constexpr auto merged_size()
{
    constexpr auto bnd0 = F0::bounds;
    constexpr auto bnd1 = F1::bounds;
    size_t i = 0,i0 = 0,i1 = 0;
    while (i0 < bnd0.size() && i1 < bnd1.size())
    {
             if (bnd0[i0] < bnd1[i1]) { i++; i0++;       }
        else if (bnd0[i0] > bnd1[i1]) { i++;       i1++; }
        else                          { i++; i0++; i1++; }
    }
    while (i0 < bnd0.size())          { i++; i0++;       }
    while (i1 < bnd1.size())          { i++;       i1++; }
    return i;
}

template <typename F0,typename F1,size_t N = merged_size<F0,F1>()>
inline static constexpr auto merge_bounds()
{
    std::array<size_t,N> merged = { 0 };

    constexpr auto bnd0 = F0::bounds;
    constexpr auto bnd1 = F1::bounds;
    size_t i = 0,i1 = 0;
    while (i0 < bnd0.size() && i1 < bnd1.size())
    {
             if (bnd0[i0] < bnd1[i1]) { merged[i++] = bnd0[i0++];             }
        else if (bnd0[i0] > bnd1[i1]) { merged[i++] =             bnd1[i1++]; }
        else                          { merged[i++] = bnd0[i0++];      i1++;  }
    }
    while (i0 < bnd0.size())          { merged[i++] = bnd0[i0++];             }
    while (i1 < bnd1.size())          { merged[i++] =             bnd1[i1++]; }

    return std::move(merged);
}

int main(int argc,char * argv[])
{
    std::cout << merged_size<F_A,F_B>() << "," << merged_size<F_B,F_C>() << "," << merged_size<F_A,F_C>() << std::endl;
    for (auto i : merge_bounds<F_A,F_B>()) std::cout << i << " ";
    std::cout <<"\n";
    for (auto i : merge_bounds<F_B,F_C>()) std::cout << i << " ";
    std::cout <<"\n";
    for (auto i : merge_bounds<F_A,F_C>()) std::cout << i << " ";
    std::cout <<"\n";

    return 0;
}

如何概括 merge_bounds 以允许将任意数量的此类结构指定为模板参数?

解决方法

拥抱价值观。

template<class T,std::size_t N>
struct partial_array:std::array<T,N>{
  std::size_t partial=N;
  constexpr std::size_t size()const{return partial;}
  constexpr T* end()const{return this->begin()+partial;}
  //etc
};

template<class T,std::size_t N,std::size_t M,std::size_t...Ms>
constexpr partial_array<T,N+M> merge(partial_array<T,N>,partial_array<T,M>);
template<class T,std::size_t M>
constexpr partial_array<T,N+(M+Ms...)> merge(partial_array<T,N> a,M> b,Ms>... cs){
  return merge( a,merge(b,cs...) );
}

现在您只需将数组转换为部分数组,然后合并它们。结果是具有 constexpr 大小的 constexpr 部分数组。

将 constexpr 大小转换为数组边界,然后将数据复制过来。

template <class...Ts>
constexpr auto merge_bounds() {
  constexpr auto merged = merge(partial_array{Ts::bounds}...);// do some magic to make this compile; maybe deduction guilds and a ctor?
  std::array<T,merged.size()> retval = merged; // add an operator std::array<T,X> to partial array
  return retval;
}

代码可能有很多错别字,但我希望你能明白。