如何构建高效的命名数组?
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
.
的所有接口函数可以避免暴露数组成员
然而,一个明显的缺点是
gadgets[Test1::b]
不像 gadgets.member().b
和 那样漂亮
- 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]();
}
我想要一个既有命名成员又可迭代的类型,因此应该能够通过索引、标签或 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
.
然而,一个明显的缺点是
gadgets[Test1::b]
不像gadgets.member().b
和 那样漂亮
- 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]();
}