Is there a way to get the memory/speed benefit of fixed length (known at compile-time) arrays while still having the static error checking and readability provided by enum class when indexing an array?
I exactly want the behavior of the array, except I want to disallow users from indexing by integers. I'd prefer to force writing potentially_descriptive_name[kLogicalRisingEdge]
over potentially_descriptive_name[0]
as I work with physicists who prioritize new results over writing maintainable codebase since they only need to work on it for 5-7 years before never seeing it again.
I'm hoping to have the ability to quickly iterate over subsets of data an arbitrary number of times (in my field, the filtered subset is called a "cut" of the data), but I need to perform this preprocessing first and I thought it would be great for readability to have an enum for the cut, so I can have something like what's below:
- have a header defining the cuts
// data_cuts.h
namespace data_cuts {
enum TimeCut {kInTime, kOutOfTime, kOther, N_elems}; // or enum class : uint?
TimeCut GetTimeCut(const Datum& d);
enum EdgeCut {kRising, kFalling, N_elems}; // sim
EdgeCut GetEdgeCut(const Datum& d);
}
- filling the data like this in one part of the project
for (const auto& d : full_data)
data_by_time_cut[GetTimeCut(d)].push_back(datum);
// potentially a much less memory intensive way to do this, but that should be addressed elsewhere
- reading said data in another part of the project
using namespace data_cuts;
for (TimeCut cut : std::vector<TimeCut>{kInTime, kOutOfTime})
histograms_by_time_cut[cut].FillN(data_by_time_cut[c]);
for (const auto& d : data_by_time_cut[kInTime])
histograms_by_time_cut[kInTime].FillSpecial(d);
but I don't know how to choose the type for data_by_time_cut
and histograms_by_time_cut
Options I can think of, (I think improving as it goes down)
I could use
std::map<CutType, T>
andenum class CutType
and indexing is only permissible withCutType
and not numeric-int or another type of cut, but I have all of map's overhead.I could use
std::vector
and convert the strongly typed enum to underlying every time I access, but it feels ungainly and it doesn't have the static check for generic integer indexing.I could use
std::array
and convert the strongly typed enum to underlying again.I could [learn how to and] write a template that wraps
std::array
and overloadsoperator[](T t)
to callto_underlying
and then I'm doing the above but less verbose. However, this just seems so generic, is this already implemented in a "better way" in C++14?
This is my attempt at the last option I listed, uses the code found here to define to_underlying
#include <array>
#include <vector>
template <typename E>
constexpr auto to_underlying(E e) noexcept
{
return static_cast<std::underlying_type_t<E>>(e);
}
template <typename T, typename U>
class EnumIndexedArray
{
public:
EnumIndexedArray(){}; // should I add the other constructors?
U& operator[](const T& t) {return m_arr[to_underlying(t)]; };
const U& operator[](const T& t) const {return m_arr[to_underlying(t)]; };
private:
std::array<T, to_underlying(T::N_elems)> m_arr;
};
namespace data_cuts {
enum class TimeCut : char {kInTime, kOutOfTime, kOther, N_elems};
enum class EdgeCut : char {kRising, kFalling, N_elems};
}
class Datum;
class Histogram;
using Data = std::vector<Datum>;
EnumIndexedArray<data_cuts::TimeCut, Data> data_by_time_cut;
EnumIndexedArray<data_cuts::TimeCut, Histogram> histograms_by_time_cut;
I wonder if I'm adding too much for this to be a general question, so I'll stop for now.