确定模板中结构或元组的成员偏移量

编程入门 行业动态 更新时间:2024-10-19 20:33:59
本文介绍了确定模板中结构或元组的成员偏移量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我想编写一个将表写入HDF5文件的模板函数。 签名应类似

template<typename record> void writeTable(const std::vector<record>& data);

其中Record是结构,或

template<typename... elements> void writeTable(const std::vector<std::tuple<elements...>>& data);

实际实现将有更多参数来确定目标等。

要编写数据,我需要定义一个HDF5复合类型,它包含成员的名称和偏移量。通常您会使用HOFFSET宏来获取字段偏移量,但由于我事先不知道结构字段,所以我不能这样做。

到目前为止,我尝试的是从typeName包构造一个结构类型。朴素的实现没有标准的布局,但实现here有。剩下的就是获得成员的偏移量。我想将参数包展开为具有偏移量的初始化列表:

#include <vector> template<typename... members> struct record {}; template<typename member, typename... members> struct record<member, members...> : record<members...> { record(member m, members... ms) : record<members...>(ms...), tail(m) {} member tail; }; template<typename... Args> void make_table(const std::string& name, const std::vector<record<Args...>>& data) { using record_type = record<Args...>; std::vector<size_t> offsets = { get_offset(record_type,Args)... }; } int main() { std::vector<record<int, float>> table = { {1, 1.0}, {2, 2.0} }; make_table("table", table); } get_offset是否有可能的实现?我不这么认为,因为在record<int, int>的情况下,它是模棱两可的。还有别的办法吗?

或者有没有其他方法可以解决这个问题?

推荐答案

计算偏移量非常简单。给定类型为T0、T1的元组...TN.T0的偏移量是0(只要您在char数组上使用alignas(T0))。T1的偏移量是向上舍入到alignof(T1)的sizeof(T0)。

一般TB(位于TA之后)的偏移量为round_up(offset_of<TA>() + sizeof(TA), alignof(TB))。

计算std::tuple中元素的偏移量可以如下所示:

constexpr size_t roundup(size_t num, size_t multiple) { const size_t mod = num % multiple; return mod == 0 ? num : num + multiple - mod; } template <size_t I, typename Tuple> struct offset_of { static constexpr size_t value = roundup( offset_of<I - 1, Tuple>::value + sizeof(std::tuple_element_t<I - 1, Tuple>), alignof(std::tuple_element_t<I, Tuple>) ); }; template <typename Tuple> struct offset_of<0, Tuple> { static constexpr size_t value = 0; }; template <size_t I, typename Tuple> constexpr size_t offset_of_v = offset_of<I, Tuple>::value;

这是一个测试套件。正如您从第一个测试中看到的,元素的对齐被考虑在内。

static_assert(offset_of_v<1, std::tuple<char, long double>> == 16); static_assert(offset_of_v<2, std::tuple<char, char, long double>> == 16); static_assert(offset_of_v<3, std::tuple<char, char, char, long double>> == 16); static_assert(offset_of_v<4, std::tuple<char, char, char, char, long double>> == 16); static_assert(offset_of_v<0, std::tuple<int, double, int, char, short, long double>> == 0); static_assert(offset_of_v<1, std::tuple<int, double, int, char, short, long double>> == 8); static_assert(offset_of_v<2, std::tuple<int, double, int, char, short, long double>> == 16); static_assert(offset_of_v<3, std::tuple<int, double, int, char, short, long double>> == 20); static_assert(offset_of_v<4, std::tuple<int, double, int, char, short, long double>> == 22); static_assert(offset_of_v<5, std::tuple<int, double, int, char, short, long double>> == 32);

我在上面的测试中硬编码了偏移量。如果以下测试成功,则偏移量正确。

static_assert(sizeof(char) == 1 && alignof(char) == 1); static_assert(sizeof(short) == 2 && alignof(short) == 2); static_assert(sizeof(int) == 4 && alignof(int) == 4); static_assert(sizeof(double) == 8 && alignof(double) == 8); static_assert(sizeof(long double) == 16 && alignof(long double) == 16);

std::tuple似乎按顺序存储它的元素(没有对它们进行排序以优化填充)。以下测试证明了这一点。我不认为该标准要求以这种方式实现std::tuple,因此我不认为以下测试一定会成功。

template <size_t I, typename Tuple> size_t real_offset(const Tuple &tup) { const char *base = reinterpret_cast<const char *>(&tup); return reinterpret_cast<const char *>(&std::get<I>(tup)) - base; } int main(int argc, char **argv) { using Tuple = std::tuple<int, double, int, char, short, long double>; Tuple tup; assert((offset_of_v<0, Tuple> == real_offset<0>(tup))); assert((offset_of_v<1, Tuple> == real_offset<1>(tup))); assert((offset_of_v<2, Tuple> == real_offset<2>(tup))); assert((offset_of_v<3, Tuple> == real_offset<3>(tup))); assert((offset_of_v<4, Tuple> == real_offset<4>(tup))); assert((offset_of_v<5, Tuple> == real_offset<5>(tup))); }

既然我已经完成了所有这些工作,real_offset函数是否适合您的需求?

这是访问char[]和offset_of的元组的最小实现。但由于reinterpret_cast,这是未定义的行为。即使我在相同的字节中构造对象并在相同的字节中访问对象,它仍然是UB。有关所有标准代码,请参阅this answer。它可以在您能找到的所有编译器上运行,但它是UB,所以无论如何都要使用它。这个元组是标准布局(与std::tuple不同)。如果元组的元素都是普通可复制的,则可以删除复制和移动构造函数,并将其替换为memcpy。

template <typename... Elems> class tuple; template <size_t I, typename Tuple> struct tuple_element; template <size_t I, typename... Elems> struct tuple_element<I, tuple<Elems...>> { using type = std::tuple_element_t<I, std::tuple<Elems...>>; }; template <size_t I, typename Tuple> using tuple_element_t = typename tuple_element<I, Tuple>::type; template <typename Tuple> struct tuple_size; template <typename... Elems> struct tuple_size<tuple<Elems...>> { static constexpr size_t value = sizeof...(Elems); }; template <typename Tuple> constexpr size_t tuple_size_v = tuple_size<Tuple>::value; constexpr size_t roundup(size_t num, size_t multiple) { const size_t mod = num % multiple; return mod == 0 ? num : num + multiple - mod; } template <size_t I, typename Tuple> struct offset_of { static constexpr size_t value = roundup( offset_of<I - 1, Tuple>::value + sizeof(tuple_element_t<I - 1, Tuple>), alignof(tuple_element_t<I, Tuple>) ); }; template <typename Tuple> struct offset_of<0, Tuple> { static constexpr size_t value = 0; }; template <size_t I, typename Tuple> constexpr size_t offset_of_v = offset_of<I, Tuple>::value; template <size_t I, typename Tuple> auto &get(Tuple &tuple) noexcept { return *reinterpret_cast<tuple_element_t<I, Tuple> *>(tuple.template addr<I>()); } template <size_t I, typename Tuple> const auto &get(const Tuple &tuple) noexcept { return *reinterpret_cast<tuple_element_t<I, Tuple> *>(tuple.template addr<I>()); } template <typename... Elems> class tuple { alignas(tuple_element_t<0, tuple>) char storage[offset_of_v<sizeof...(Elems), tuple<Elems..., char>>]; using idx_seq = std::make_index_sequence<sizeof...(Elems)>; template <size_t I> void *addr() { return static_cast<void *>(&storage + offset_of_v<I, tuple>); } template <size_t I, typename Tuple> friend auto &get(const Tuple &) noexcept; template <size_t I, typename Tuple> friend const auto &get(Tuple &) noexcept; template <size_t... I> void default_construct(std::index_sequence<I...>) { (new (addr<I>()) Elems{}, ...); } template <size_t... I> void destroy(std::index_sequence<I...>) { (get<I>(*this).~Elems(), ...); } template <size_t... I> void move_construct(tuple &&other, std::index_sequence<I...>) { (new (addr<I>()) Elems{std::move(get<I>(other))}, ...); } template <size_t... I> void copy_construct(const tuple &other, std::index_sequence<I...>) { (new (addr<I>()) Elems{get<I>(other)}, ...); } template <size_t... I> void move_assign(tuple &&other, std::index_sequence<I...>) { (static_cast<void>(get<I>(*this) = std::move(get<I>(other))), ...); } template <size_t... I> void copy_assign(const tuple &other, std::index_sequence<I...>) { (static_cast<void>(get<I>(*this) = get<I>(other)), ...); } public: tuple() noexcept((std::is_nothrow_default_constructible_v<Elems> && ...)) { default_construct(idx_seq{}); } ~tuple() { destroy(idx_seq{}); } tuple(tuple &&other) noexcept((std::is_nothrow_move_constructible_v<Elems> && ...)) { move_construct(other, idx_seq{}); } tuple(const tuple &other) noexcept((std::is_nothrow_copy_constructible_v<Elems> && ...)) { copy_construct(other, idx_seq{}); } tuple &operator=(tuple &&other) noexcept((std::is_nothrow_move_assignable_v<Elems> && ...)) { move_assign(other, idx_seq{}); return *this; } tuple &operator=(const tuple &other) noexcept((std::is_nothrow_copy_assignable_v<Elems> && ...)) { copy_assign(other, idx_seq{}); return *this; } };

或者,您可以使用此函数:

template <size_t I, typename Tuple> size_t member_offset() { return reinterpret_cast<size_t>(&std::get<I>(*static_cast<Tuple *>(nullptr))); } template <typename Member, typename Class> size_t member_offset(Member (Class::*ptr)) { return reinterpret_cast<size_t>(&(static_cast<Class *>(nullptr)->*ptr)); } template <auto MemPtr> size_t member_offset() { return member_offset(MemPtr); } 再说一次,这是未定义的行为(因为nullptr取消引用和reinterpret_cast),但它将在每个主要编译器中按预期工作。该函数不能为constexpr(即使成员偏移量是编译时计算)。

更多推荐

确定模板中结构或元组的成员偏移量

本文发布于:2023-11-12 04:06:44,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1580474.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:成员   模板   结构   偏移量

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!