First, you are violating DRY.
Replace this:
std::vector<std::array<byte_t, 1>> arr_1;
std::vector<std::array<byte_t, 2>> arr_2;
... up to ....
std::vector<std::array<byte_t, 50>> arr_50;
with something like:
template<typename Pack, unsigned X> struct append;
template<template<unsigned...>class Pack, unsigned... Xs, unsigned X>
struct append<Pack<Xs...>, X> {
typedef Pack<Xs..., X> type;
};
template<typename Pack, unsigned X> using Append=typename append<Pack,X>::type;
template<unsigned N, template<unsigned>class Contents>
struct auto_tuple {
typedef Append< typename auto_tuple<N-1, Contents>::type, Contents<N-1> > type;
};
template<template<unsigned>class Contents>
struct auto_tuple<0, Contents> {
typedef std::tuple<> type;
};
template<unsigned N, template<unsigned>class Contents>
using AutoTuple = typename auto_tuple<N,Contents>::type;
where AutoTuple<N, Contents>
applies 0
through N-1
to Contents
and generates a std::tuple
out of it. Write:
template<typename T, unsigned N>
using vec_of_array = std::vector<std::array<T, N>>;
template<unsigned N>
using vec_of_byte_array = vec_of_array<byte_t, N>;
template<unsigned N>
using get_nth_byte_array = vec_of_byte_array<N+1>;
which you use to populate your tuple
like:
typedef AutoTuple<50, get_nth_byte_array> my_tuple;
which is a std::tuple
of 50
different std::vector
s, each of which store a std::array
of 1
through 50
byte
s.
This took far fewer than 50
lines, and while this will be harder to get it to work and understand it, it means that the code is uniform and generated once: there is far less of a chance that one particular line is wrong, and a far higher chance that everything is wrong at once. And you can use a compile-time value to extract the nth element via std::get<7>(my_tuple)
. Oh, and if you want 100
or 10
instead of 50
? Change one constant.
Next, a contiguous_range
structure that is basically a dressed up pair of pointers gives you a type-erased way to look at an array (or other buffer) of N
elements, like Passing a std::array of unknown size to a function
Now, you can either write your big switch statement manually, or you can build a table of function pointers that extract the contiguous_range
and build it automatically.
// DataStorage is the tuple of std::vector of std::array
template<unsigned...> struct seq{};
template<unsigned max, unsigned... s> struct make_seq:make_seq<max-1, max-1, s...> {};
template<unsigned... s> struct make_seq<0,s...>:seq<s...> {};
template<unsigned N>
struct get_array {
static contig_range<byte> get(DataStorage& data, unsinged idx) {
return ( std::get<N>( std::forward<Data>(data) )[idx];
}
};
and build an array of 50 of them (each with a different N
) stored in an array of:
typedef contig_range<byte> (*array_getter)( DataStorage&, unsigned idx );
template<unsigned N, unsigned... S>
std::array< array_getter, N > populate_array_helper( seq<S...> ) {
return { get_array<S>::get... };
}
template<unsigned N>
std::array< array_getter, N > populate_array() {
return populate_array_helper( make_seq<N>() );
}
which you can then use the run-time argument to lookup, call it, and you get a type-erased instance of your contiguous array of byte
.
Non-code overhead is 50 pointers (for the lookup table), 3 pointers (for the containing vector).
And you never do a cpoy/paste of the same code 50 times over with a single number changed each time, one of which has a slight typo in it that generates a subtle fenceposting bug that only occurs in one obscure, hard to reproduce test case in release mode only.