Как построить эффективный именованный массив? - PullRequest
3 голосов
/ 20 февраля 2020

Я хочу иметь тип, который имеет именованные элементы, а также является итеративным, поэтому нужно иметь возможность ссылаться на элементы по индексу, по метке или по каждому для l oop. Один из способов реализовать это - использовать std::unordered_map<std::string,T> с некоторыми вспомогательными данными для индексов. Понятно, что это будет ужасно неэффективно, поскольку для каждого доступа члена вам нужно иметь sh a std::string.

Моя текущая попытка выглядит следующим образом:

// 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)];
    }   
  };  
}

Что можно использовать как поэтому:

  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]();

Можно избежать раскрытия элемента массива, перенаправив все функции интерфейса std::array.

Однако очевидным недостатком является то, что

  1. gadgets[Test1::b] не так хорош, как что-то вроде gadgets.member().b, а
  2. в заголовочном файле c ++ (который очень вонючий) есть #define

Есть ли способ иметь именованный массив с такой же производительностью, как у std::array?

Ответы [ 2 ]

4 голосов
/ 20 февраля 2020

Minimalisti c пример того, как это можно сделать:

#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>();

} 
0 голосов
/ 20 февраля 2020

Просто определите перечисления, как вы хотите их. И оставить их незаданными - это хорошо, вы хотите, чтобы имена попали в область объявления, и вам помогает неявное преобразование в std::size_t.

template <typename K, typename T>
using NamedArray = std::array<T, K::NUM>;

Тогда

struct Gadget {
  int i;
  Gadget(int i) : i(i) {}
  void operator()() const {
    std::cout << "Gadget(" << i << ")\n";
  }   
};  

enum Test1 : std::size_t { a, b, c, d, NUM };

int main() {
    NamedArray<Test1,Gadget> gadgets { 0,1,2,3 };
    // for each works:
    for (auto const& gadget: gadgets) {
      gadget();
    }
    // named access works:
    gadgets[b]();
    // access by index works:
    gadgets[1]();
}
...