I want to have a type that both has named members and also is iterable, so one should be able to refer to the members by index, by label or from a for-each loop. One way to realise this would be to use std::unordered_map<std::string,T>
with some helper data for the indices. Clearly this would be horribly inefficient as for each member access you need to hash a std::string
.
My current attempt looks like this:
// named-array.h
#pragma once
#include <array>
#include <cstddef>
#define NamedArray_KeyDecl(Name, ...) enum class Name : std::size_t { __VA_ARGS__, NUM }
namespace util {
template <typename K, typename T>
struct NamedArray {
static constexpr std::size_t cast(K k) {
return static_cast<std::size_t>(k);
}
std::array<T,cast(K::NUM)> array;
NamedArray(std::array<T,cast(K::NUM)> a) : array(a) {
}
constexpr T& operator[](K k) {
return array[cast(k)];
}
constexpr T const& operator[](K k) const {
return array[cast(k)];
}
};
}
Which can be used like so:
struct Gadget {
int i;
Gadget(int i) : i(i) {}
void operator()() const {
std::cout << "Gadget(" << i << ")\n";
}
};
NamedArray_KeyDecl(Test1, a,b,c,d);
util::NamedArray<Test1,Gadget> gadgets {{0,1,2,3}};
// for each works:
for (auto const& gadget: gadgets.array) {
gadget();
}
// named access works:
gadgets[Test1::b]();
// access by index works:
gadgets.array[1]();
Exposing the array member could be avoided by forwarding all interface functions of std::array
.
However, an obvious drawback is that
gadgets[Test1::b]
is not as pretty as something along the lines of gadgets.member().b
and#define
in a c++ header file (which is extremely smelly)Is there a way to have a named array with the same performance as an std::array
?
Minimalistic example of how it could be done:
#include <array>
#include <type_traits>
template<class Tag, class...Tags>
struct position {
};
template<class Tag, class...Tags>
struct position<Tag, Tag, Tags...> {
constexpr static unsigned value = 0;
};
template<class Tag, class First, class...Tags>
struct position<Tag, First, Tags...> {
constexpr static unsigned value = 1 + position<Tag, Tags...>::value;
};
template<class T, class...Tags>
class NamedArray {
public:
template<class U>
constexpr T& operator[](U tag) {
return array_[position<U, Tags...>::value];
}
constexpr T& operator[](unsigned val) {
return array_[val];
}
template<class U>
constexpr T& member(U u = U{}) {
return (*this)[u];
}
private:
std::array<T, sizeof...(Tags)> array_;
};
struct tag1{};
struct tag2{};
int main() {
NamedArray<int, tag1, tag2> a;
a[tag1{}];
a[tag2{}];
a.member(tag1{});
a.member<tag1>();
}