如何构建高效的命名数组?

How to build an efficient named array?

我想要一个既有命名成员又可迭代的类型,因此应该能够通过索引、标签或 for-each 循环来引用成员。实现这一点的一种方法是使用 std::unordered_map<std::string,T> 和索引的一些辅助数据。显然,这将是非常低效的,因为对于每个成员访问,您需要散列一个 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. 那样漂亮
  3. c++头文件中有暴露的#define(非常臭)

有没有办法让命名数组的性能与 std::array 相同?

如何实现的简单示例:

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

} 

只需根据需要定义枚举即可。并且让它们不受范围限制是可以的,您 想要 名称泄漏到声明的范围内,并且 帮助 隐式转换为 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]();
}