【问题标题】:How to build an efficient named array?如何构建一个高效的命名数组?
【发布时间】:2020-02-20 08:11:38
【问题描述】:

我希望有一种类型既具有命名成员又是可迭代的,因此应该能够通过索引、标签或 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. c++ 头文件中有一个暴露的#define(非常臭)

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

【问题讨论】:

  • 有没有可能——当然有。检查boost::multi_index 的样子。每个索引都有标签类型。我想这是你可能想要的东西
  • @bartop 它看起来更像样板。据我所知,它标记其成员的方式是创建一堆空结构。而且按索引访问似乎很轻量级。
  • 我并不是说要使用multi_index,而只是将其用作示例。无论如何,如果你不想要宏,我认为标签类型是唯一可行的选择

标签: c++ arrays performance named


【解决方案1】:

如何完成的简约示例:

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

} 

【讨论】:

  • 啊,现在我明白了。是的,这好多了。不是最优的(因为空结构),但一个很好的解决方案!
【解决方案2】:

只需根据需要定义枚举。并且让它们不作用域很好,您希望将名称泄漏到声明的作用域中,并且通过隐式转换为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]();
}

【讨论】:

    猜你喜欢
    • 2011-06-22
    • 2019-05-22
    • 1970-01-01
    • 1970-01-01
    • 2021-12-17
    • 2017-04-25
    • 2011-10-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多