Search code examples
c++arraysperformancenamed

How to build an efficient named array?


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

  1. gadgets[Test1::b] is not as pretty as something along the lines of gadgets.member().b and
  2. there is an exposed #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?


Solution

  • 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>();
    
    }