1

I have a bunch of structs holding pre-sorted std::arrays of varying number of size_ts. As a toy example, suppose we've got the following three structs:

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, 250, 300, 301 }; };

The goal is to perform the equivalent of an N-way std::set_union at compilation time; e.g., given the above structs, I want to be able to write

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

and end up with bounds as a constexpr std::array<size_t, 8> containing values 0, 100, 125, 200, 250, 300, 301, 500.

It was pretty easy to make this work for merging the bounds arrays from a pair of structs; however, I am at a bit of a loss regarding how best to generalize this to use variadic templates and parameter packs. To get the version with pairs working, I resort to first "simulating" a merge to determine how long the merged array will be before actually doing the merge, but this approach gets awfully hairy when combining with parameter packs. (I suspect even my code for pairs is far less elegant than it would be if I had a better handle on some of the relevant language features...)

Here's an MWE demonstrating my functioning code for pairs:

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

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, 250, 300, 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, i0 = 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;
}

How can I generalize merge_bounds to allow an arbitrary number of such structs to be specified as template parameters?

1 Answers1

0

Embrace values.

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 N, std::size_t M>
constexpr partial_array<T,N+(M+Ms...)> merge(partial_array<T,N> a,partial_array<T,M> b, partial_array<T,Ms>... cs){
  return merge( a, merge(b,cs...) );
}

Now you just take the arrays, turn them to partial arrays, merge them. The result is a constexpr partial array with a constexpr size.

Convert that constexpr size into an array bounds, and copy the data over.

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;
}

code is probably full of typos, but I hope you get the idea.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524