Determine member offset of struct or tuple in template Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 23, 2019 at 23:30 UTC (7:30pm US/Eastern) Data science time! April 2019 and salary with experience The Ask Question Wizard is Live!Casting a char array to an object pointer - is this UB?Storing C++ template function definitions in a .CPP fileWhy isn't sizeof for a struct equal to the sum of sizeof of each member?Use 'class' or 'typename' for template parameters?Is it possible to write a template to check for a function's existence?Why can templates only be implemented in the header file?Where and why do I have to put the “template” and “typename” keywords?Difference between 'struct' and 'typedef struct' in C++?Enable method based on boolean template parameterError while trying to use const int std::arrayPassing variadic template to pthread_create
In musical terms, what properties are varied by the human voice to produce different words / syllables?
What is best way to wire a ceiling receptacle in this situation?
Strange behavior of Object.defineProperty() in JavaScript
What to do with repeated rejections for phd position
Why can't I install Tomboy in Ubuntu Mate 19.04?
The test team as an enemy of development? And how can this be avoided?
What are the discoveries that have been possible with the rejection of positivism?
Amount of permutations on an NxNxN Rubik's Cube
Did any compiler fully use 80-bit floating point?
What does Turing mean by this statement?
How long can equipment go unused before powering up runs the risk of damage?
How can I prevent/balance waiting and turtling as a response to cooldown mechanics
Why are vacuum tubes still used in amateur radios?
The Nth Gryphon Number
macOS: Name for app shortcut screen found by pinching with thumb and three fingers
Misunderstanding of Sylow theory
Is CEO the "profession" with the most psychopaths?
Why does it sometimes sound good to play a grace note as a lead in to a note in a melody?
Drawing spherical mirrors
How to report t statistic from R
What's the meaning of "fortified infraction restraint"?
What is an "asse" in Elizabethan English?
preposition before coffee
How to write capital alpha?
Determine member offset of struct or tuple in template
Announcing the arrival of Valued Associate #679: Cesar Manara
Planned maintenance scheduled April 23, 2019 at 23:30 UTC (7:30pm US/Eastern)
Data science time! April 2019 and salary with experience
The Ask Question Wizard is Live!Casting a char array to an object pointer - is this UB?Storing C++ template function definitions in a .CPP fileWhy isn't sizeof for a struct equal to the sum of sizeof of each member?Use 'class' or 'typename' for template parameters?Is it possible to write a template to check for a function's existence?Why can templates only be implemented in the header file?Where and why do I have to put the “template” and “typename” keywords?Difference between 'struct' and 'typedef struct' in C++?Enable method based on boolean template parameterError while trying to use const int std::arrayPassing variadic template to pthread_create
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
I want to write a template function that writes tables to HDF5 files.
The signature should look similar to
template<typename record> void writeTable(const std::vector<record>& data);
where record is a struct, or
template<typename... elements>
void writeTable(const std::vector<std::tuple<elements...>>& data);
The actual implementation would have more parameters to determine the destionation, etc.
To write the data I need to define a HDF5 compound type, which contains the name and the offset of the members. Usually you would use the HOFFSET
macro the get the field offset, but as I don't know the struct fields beforehand I can't do that.
What I tried so far was constructing a struct type from the typename pack. The naive implementation did not have standard layout, but the implementation here does. All that's left is get the offsets of the members. I would like to expand the parameter pack into an initializer list with the offsets:
#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);
Is there a possible implementation for get_offset
? I would think not, because in the case of record<int, int>
it would be ambiguous. Is there another way to do it?
Or is there any other way I could approach this problem?
c++ templates variadic-templates template-meta-programming
add a comment |
I want to write a template function that writes tables to HDF5 files.
The signature should look similar to
template<typename record> void writeTable(const std::vector<record>& data);
where record is a struct, or
template<typename... elements>
void writeTable(const std::vector<std::tuple<elements...>>& data);
The actual implementation would have more parameters to determine the destionation, etc.
To write the data I need to define a HDF5 compound type, which contains the name and the offset of the members. Usually you would use the HOFFSET
macro the get the field offset, but as I don't know the struct fields beforehand I can't do that.
What I tried so far was constructing a struct type from the typename pack. The naive implementation did not have standard layout, but the implementation here does. All that's left is get the offsets of the members. I would like to expand the parameter pack into an initializer list with the offsets:
#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);
Is there a possible implementation for get_offset
? I would think not, because in the case of record<int, int>
it would be ambiguous. Is there another way to do it?
Or is there any other way I could approach this problem?
c++ templates variadic-templates template-meta-programming
add a comment |
I want to write a template function that writes tables to HDF5 files.
The signature should look similar to
template<typename record> void writeTable(const std::vector<record>& data);
where record is a struct, or
template<typename... elements>
void writeTable(const std::vector<std::tuple<elements...>>& data);
The actual implementation would have more parameters to determine the destionation, etc.
To write the data I need to define a HDF5 compound type, which contains the name and the offset of the members. Usually you would use the HOFFSET
macro the get the field offset, but as I don't know the struct fields beforehand I can't do that.
What I tried so far was constructing a struct type from the typename pack. The naive implementation did not have standard layout, but the implementation here does. All that's left is get the offsets of the members. I would like to expand the parameter pack into an initializer list with the offsets:
#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);
Is there a possible implementation for get_offset
? I would think not, because in the case of record<int, int>
it would be ambiguous. Is there another way to do it?
Or is there any other way I could approach this problem?
c++ templates variadic-templates template-meta-programming
I want to write a template function that writes tables to HDF5 files.
The signature should look similar to
template<typename record> void writeTable(const std::vector<record>& data);
where record is a struct, or
template<typename... elements>
void writeTable(const std::vector<std::tuple<elements...>>& data);
The actual implementation would have more parameters to determine the destionation, etc.
To write the data I need to define a HDF5 compound type, which contains the name and the offset of the members. Usually you would use the HOFFSET
macro the get the field offset, but as I don't know the struct fields beforehand I can't do that.
What I tried so far was constructing a struct type from the typename pack. The naive implementation did not have standard layout, but the implementation here does. All that's left is get the offsets of the members. I would like to expand the parameter pack into an initializer list with the offsets:
#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);
Is there a possible implementation for get_offset
? I would think not, because in the case of record<int, int>
it would be ambiguous. Is there another way to do it?
Or is there any other way I could approach this problem?
c++ templates variadic-templates template-meta-programming
c++ templates variadic-templates template-meta-programming
edited Mar 9 at 16:58
max66
39.6k74575
39.6k74575
asked Mar 8 at 22:05
mauflmaufl
1841314
1841314
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
Not sure to understand what do you exactly want but... what about using recursion based on a index sequence (starting from C++14) something as follows?
#include <vector>
#include <utility>
#include <iostream>
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 <std::size_t, typename, std::size_t = 0u>
struct get_offset;
template <std::size_t N, typename A0, typename ... As, std::size_t Off>
struct get_offset<N, record<A0, As...>, Off>
: public get_offset<N-1u, record<As...>, Off+sizeof(A0)>
;
template <typename A0, typename ... As, std::size_t Off>
struct get_offset<0u, record<A0, As...>, Off>
: public std::integral_constant<std::size_t, Off>
;
template <typename... Args, std::size_t ... Is>
auto make_table_helper (std::string const & name,
std::vector<record<Args...>> const & data,
std::index_sequence<Is...> const &)
return std::vector<std::size_t> get_offset<Is, record<Args...>>::value... ;
template <typename... Args>
auto make_table (std::string const & name,
std::vector<record<Args...>> const & data)
return make_table_helper(name, data, std::index_sequence_for<Args...>);
int main ()
std::vector<record<int, float>> table = 1, 1.0, 2, 2.0 ;
auto v = make_table("table", table);
for ( auto const & o : v )
std::cout << o << ' ';
std::cout << std::endl;
Unfortunately isn't an efficient solution because the last value is calculated n-times.
1
This doesn't take into account the alignment though? If the type doesn't have standard layout, is there any guarantee of the offset at all?
– maufl
Mar 11 at 9:21
@maufl - right; just to give an idea of the recursion part; for the alignment, instead ofOff+sizeof(A0)
, I suppose you can use something asOff+roundup(A0, alignof(A0))
(se theroundup()
in the Kerndog73 answer) but, also in that case, I'm not sure that you can have guarantee at all.
– max66
Mar 11 at 10:09
add a comment |
Calculating offsets is quite simple. Given a tuple with types T0, T1 ... TN. The offset of T0
is 0
(as long as you use alignas(T0)
on your char
array. The offset of T1
is the sizeof(T0)
rounded up to alignof(T1)
.
In general, the offset of TB
(which comes after TA
) is round_up(offset_of<TA>() + sizeof(TA), alignof(TB))
.
Calculating the offsets of elements in a std::tuple
could be done like this:
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;
Here's a test suite. As you can see from the first test, the alignment of elements is taken into account.
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);
I hardcoded the offsets in the above tests. The offsets are correct if the following tests succeed.
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
seems to store it's elements sequentially (without sorting them to optimize padding). That's proven by the following tests. I don't think the standard requires std::tuple
to be implemented this way so I don't think the following tests are guaranteed to succeed.
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)));
Now that I've gone to all of this effort, would that real_offset
function suit your needs?
This is a minimal implementation of a tuple that accesses a char[]
with offset_of
. This is undefined behavior though because of the reinterpret_cast
. Even though I'm constructing the object in the same bytes and accessing the object in the same bytes, it's still UB. See this answer for all the standardese. It will work on every compiler you can find but it's UB so just use it anyway. This tuple is standard layout (unlike std::tuple
). If the elements of your tuple are all trivially copyable, you can remove the copy and move constructors and replace them with 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>()) Elemsstd::move(get<I>(other)), ...);
template <size_t... I>
void copy_construct(const tuple &other, std::index_sequence<I...>)
(new (addr<I>()) Elemsget<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;
;
Alternatively, you could use this function:
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);
Once again, this is undefined behavior (because of the nullptr
dereference and the reinterpret_cast
) but it will work as expected with every major compiler. The function cannot be constexpr
(even though member offset is a compile-time calculation).
This looks very good. Is the calculation in real_offset always correct? Even if the tuple does not have standard layout?
– maufl
Mar 11 at 9:23
@maufl The calculation insidereal_offset
is always correct and works for any type (not just tuples). However, it usesreinterpret_cast
so it's technically undefined behavior and it cannot be used in aconstexpr
context (even though it's a compile-time calculation). The calculation insideoffset_of
only works for standard layout types.std::tuple
is standard layout so it will work for all tuples. A standard layout type is still standard layout even if it contains non-standard layout members. I'd recommend usingoffset_of
because it's not undefined behavior.
– Kerndog73
Mar 11 at 9:43
Thanks. I checked but in gcc,std::tuple
does not have standard layout. Undefined behavior is what scares me, and why I did not pick an implementation yet.
– maufl
Mar 11 at 22:12
@maufl I just checked with clang and you're right. Damn!
– Kerndog73
Mar 11 at 22:21
@maufl Maybe you could find a way of implementing a tuple that is standard layout. One idea would be to store an array of bytes and use offset_of and placement new. That would be standard layout.
– Kerndog73
Mar 11 at 22:23
|
show 2 more comments
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55071632%2fdetermine-member-offset-of-struct-or-tuple-in-template%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
Not sure to understand what do you exactly want but... what about using recursion based on a index sequence (starting from C++14) something as follows?
#include <vector>
#include <utility>
#include <iostream>
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 <std::size_t, typename, std::size_t = 0u>
struct get_offset;
template <std::size_t N, typename A0, typename ... As, std::size_t Off>
struct get_offset<N, record<A0, As...>, Off>
: public get_offset<N-1u, record<As...>, Off+sizeof(A0)>
;
template <typename A0, typename ... As, std::size_t Off>
struct get_offset<0u, record<A0, As...>, Off>
: public std::integral_constant<std::size_t, Off>
;
template <typename... Args, std::size_t ... Is>
auto make_table_helper (std::string const & name,
std::vector<record<Args...>> const & data,
std::index_sequence<Is...> const &)
return std::vector<std::size_t> get_offset<Is, record<Args...>>::value... ;
template <typename... Args>
auto make_table (std::string const & name,
std::vector<record<Args...>> const & data)
return make_table_helper(name, data, std::index_sequence_for<Args...>);
int main ()
std::vector<record<int, float>> table = 1, 1.0, 2, 2.0 ;
auto v = make_table("table", table);
for ( auto const & o : v )
std::cout << o << ' ';
std::cout << std::endl;
Unfortunately isn't an efficient solution because the last value is calculated n-times.
1
This doesn't take into account the alignment though? If the type doesn't have standard layout, is there any guarantee of the offset at all?
– maufl
Mar 11 at 9:21
@maufl - right; just to give an idea of the recursion part; for the alignment, instead ofOff+sizeof(A0)
, I suppose you can use something asOff+roundup(A0, alignof(A0))
(se theroundup()
in the Kerndog73 answer) but, also in that case, I'm not sure that you can have guarantee at all.
– max66
Mar 11 at 10:09
add a comment |
Not sure to understand what do you exactly want but... what about using recursion based on a index sequence (starting from C++14) something as follows?
#include <vector>
#include <utility>
#include <iostream>
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 <std::size_t, typename, std::size_t = 0u>
struct get_offset;
template <std::size_t N, typename A0, typename ... As, std::size_t Off>
struct get_offset<N, record<A0, As...>, Off>
: public get_offset<N-1u, record<As...>, Off+sizeof(A0)>
;
template <typename A0, typename ... As, std::size_t Off>
struct get_offset<0u, record<A0, As...>, Off>
: public std::integral_constant<std::size_t, Off>
;
template <typename... Args, std::size_t ... Is>
auto make_table_helper (std::string const & name,
std::vector<record<Args...>> const & data,
std::index_sequence<Is...> const &)
return std::vector<std::size_t> get_offset<Is, record<Args...>>::value... ;
template <typename... Args>
auto make_table (std::string const & name,
std::vector<record<Args...>> const & data)
return make_table_helper(name, data, std::index_sequence_for<Args...>);
int main ()
std::vector<record<int, float>> table = 1, 1.0, 2, 2.0 ;
auto v = make_table("table", table);
for ( auto const & o : v )
std::cout << o << ' ';
std::cout << std::endl;
Unfortunately isn't an efficient solution because the last value is calculated n-times.
1
This doesn't take into account the alignment though? If the type doesn't have standard layout, is there any guarantee of the offset at all?
– maufl
Mar 11 at 9:21
@maufl - right; just to give an idea of the recursion part; for the alignment, instead ofOff+sizeof(A0)
, I suppose you can use something asOff+roundup(A0, alignof(A0))
(se theroundup()
in the Kerndog73 answer) but, also in that case, I'm not sure that you can have guarantee at all.
– max66
Mar 11 at 10:09
add a comment |
Not sure to understand what do you exactly want but... what about using recursion based on a index sequence (starting from C++14) something as follows?
#include <vector>
#include <utility>
#include <iostream>
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 <std::size_t, typename, std::size_t = 0u>
struct get_offset;
template <std::size_t N, typename A0, typename ... As, std::size_t Off>
struct get_offset<N, record<A0, As...>, Off>
: public get_offset<N-1u, record<As...>, Off+sizeof(A0)>
;
template <typename A0, typename ... As, std::size_t Off>
struct get_offset<0u, record<A0, As...>, Off>
: public std::integral_constant<std::size_t, Off>
;
template <typename... Args, std::size_t ... Is>
auto make_table_helper (std::string const & name,
std::vector<record<Args...>> const & data,
std::index_sequence<Is...> const &)
return std::vector<std::size_t> get_offset<Is, record<Args...>>::value... ;
template <typename... Args>
auto make_table (std::string const & name,
std::vector<record<Args...>> const & data)
return make_table_helper(name, data, std::index_sequence_for<Args...>);
int main ()
std::vector<record<int, float>> table = 1, 1.0, 2, 2.0 ;
auto v = make_table("table", table);
for ( auto const & o : v )
std::cout << o << ' ';
std::cout << std::endl;
Unfortunately isn't an efficient solution because the last value is calculated n-times.
Not sure to understand what do you exactly want but... what about using recursion based on a index sequence (starting from C++14) something as follows?
#include <vector>
#include <utility>
#include <iostream>
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 <std::size_t, typename, std::size_t = 0u>
struct get_offset;
template <std::size_t N, typename A0, typename ... As, std::size_t Off>
struct get_offset<N, record<A0, As...>, Off>
: public get_offset<N-1u, record<As...>, Off+sizeof(A0)>
;
template <typename A0, typename ... As, std::size_t Off>
struct get_offset<0u, record<A0, As...>, Off>
: public std::integral_constant<std::size_t, Off>
;
template <typename... Args, std::size_t ... Is>
auto make_table_helper (std::string const & name,
std::vector<record<Args...>> const & data,
std::index_sequence<Is...> const &)
return std::vector<std::size_t> get_offset<Is, record<Args...>>::value... ;
template <typename... Args>
auto make_table (std::string const & name,
std::vector<record<Args...>> const & data)
return make_table_helper(name, data, std::index_sequence_for<Args...>);
int main ()
std::vector<record<int, float>> table = 1, 1.0, 2, 2.0 ;
auto v = make_table("table", table);
for ( auto const & o : v )
std::cout << o << ' ';
std::cout << std::endl;
Unfortunately isn't an efficient solution because the last value is calculated n-times.
answered Mar 8 at 23:01
max66max66
39.6k74575
39.6k74575
1
This doesn't take into account the alignment though? If the type doesn't have standard layout, is there any guarantee of the offset at all?
– maufl
Mar 11 at 9:21
@maufl - right; just to give an idea of the recursion part; for the alignment, instead ofOff+sizeof(A0)
, I suppose you can use something asOff+roundup(A0, alignof(A0))
(se theroundup()
in the Kerndog73 answer) but, also in that case, I'm not sure that you can have guarantee at all.
– max66
Mar 11 at 10:09
add a comment |
1
This doesn't take into account the alignment though? If the type doesn't have standard layout, is there any guarantee of the offset at all?
– maufl
Mar 11 at 9:21
@maufl - right; just to give an idea of the recursion part; for the alignment, instead ofOff+sizeof(A0)
, I suppose you can use something asOff+roundup(A0, alignof(A0))
(se theroundup()
in the Kerndog73 answer) but, also in that case, I'm not sure that you can have guarantee at all.
– max66
Mar 11 at 10:09
1
1
This doesn't take into account the alignment though? If the type doesn't have standard layout, is there any guarantee of the offset at all?
– maufl
Mar 11 at 9:21
This doesn't take into account the alignment though? If the type doesn't have standard layout, is there any guarantee of the offset at all?
– maufl
Mar 11 at 9:21
@maufl - right; just to give an idea of the recursion part; for the alignment, instead of
Off+sizeof(A0)
, I suppose you can use something as Off+roundup(A0, alignof(A0))
(se the roundup()
in the Kerndog73 answer) but, also in that case, I'm not sure that you can have guarantee at all.– max66
Mar 11 at 10:09
@maufl - right; just to give an idea of the recursion part; for the alignment, instead of
Off+sizeof(A0)
, I suppose you can use something as Off+roundup(A0, alignof(A0))
(se the roundup()
in the Kerndog73 answer) but, also in that case, I'm not sure that you can have guarantee at all.– max66
Mar 11 at 10:09
add a comment |
Calculating offsets is quite simple. Given a tuple with types T0, T1 ... TN. The offset of T0
is 0
(as long as you use alignas(T0)
on your char
array. The offset of T1
is the sizeof(T0)
rounded up to alignof(T1)
.
In general, the offset of TB
(which comes after TA
) is round_up(offset_of<TA>() + sizeof(TA), alignof(TB))
.
Calculating the offsets of elements in a std::tuple
could be done like this:
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;
Here's a test suite. As you can see from the first test, the alignment of elements is taken into account.
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);
I hardcoded the offsets in the above tests. The offsets are correct if the following tests succeed.
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
seems to store it's elements sequentially (without sorting them to optimize padding). That's proven by the following tests. I don't think the standard requires std::tuple
to be implemented this way so I don't think the following tests are guaranteed to succeed.
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)));
Now that I've gone to all of this effort, would that real_offset
function suit your needs?
This is a minimal implementation of a tuple that accesses a char[]
with offset_of
. This is undefined behavior though because of the reinterpret_cast
. Even though I'm constructing the object in the same bytes and accessing the object in the same bytes, it's still UB. See this answer for all the standardese. It will work on every compiler you can find but it's UB so just use it anyway. This tuple is standard layout (unlike std::tuple
). If the elements of your tuple are all trivially copyable, you can remove the copy and move constructors and replace them with 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>()) Elemsstd::move(get<I>(other)), ...);
template <size_t... I>
void copy_construct(const tuple &other, std::index_sequence<I...>)
(new (addr<I>()) Elemsget<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;
;
Alternatively, you could use this function:
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);
Once again, this is undefined behavior (because of the nullptr
dereference and the reinterpret_cast
) but it will work as expected with every major compiler. The function cannot be constexpr
(even though member offset is a compile-time calculation).
This looks very good. Is the calculation in real_offset always correct? Even if the tuple does not have standard layout?
– maufl
Mar 11 at 9:23
@maufl The calculation insidereal_offset
is always correct and works for any type (not just tuples). However, it usesreinterpret_cast
so it's technically undefined behavior and it cannot be used in aconstexpr
context (even though it's a compile-time calculation). The calculation insideoffset_of
only works for standard layout types.std::tuple
is standard layout so it will work for all tuples. A standard layout type is still standard layout even if it contains non-standard layout members. I'd recommend usingoffset_of
because it's not undefined behavior.
– Kerndog73
Mar 11 at 9:43
Thanks. I checked but in gcc,std::tuple
does not have standard layout. Undefined behavior is what scares me, and why I did not pick an implementation yet.
– maufl
Mar 11 at 22:12
@maufl I just checked with clang and you're right. Damn!
– Kerndog73
Mar 11 at 22:21
@maufl Maybe you could find a way of implementing a tuple that is standard layout. One idea would be to store an array of bytes and use offset_of and placement new. That would be standard layout.
– Kerndog73
Mar 11 at 22:23
|
show 2 more comments
Calculating offsets is quite simple. Given a tuple with types T0, T1 ... TN. The offset of T0
is 0
(as long as you use alignas(T0)
on your char
array. The offset of T1
is the sizeof(T0)
rounded up to alignof(T1)
.
In general, the offset of TB
(which comes after TA
) is round_up(offset_of<TA>() + sizeof(TA), alignof(TB))
.
Calculating the offsets of elements in a std::tuple
could be done like this:
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;
Here's a test suite. As you can see from the first test, the alignment of elements is taken into account.
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);
I hardcoded the offsets in the above tests. The offsets are correct if the following tests succeed.
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
seems to store it's elements sequentially (without sorting them to optimize padding). That's proven by the following tests. I don't think the standard requires std::tuple
to be implemented this way so I don't think the following tests are guaranteed to succeed.
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)));
Now that I've gone to all of this effort, would that real_offset
function suit your needs?
This is a minimal implementation of a tuple that accesses a char[]
with offset_of
. This is undefined behavior though because of the reinterpret_cast
. Even though I'm constructing the object in the same bytes and accessing the object in the same bytes, it's still UB. See this answer for all the standardese. It will work on every compiler you can find but it's UB so just use it anyway. This tuple is standard layout (unlike std::tuple
). If the elements of your tuple are all trivially copyable, you can remove the copy and move constructors and replace them with 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>()) Elemsstd::move(get<I>(other)), ...);
template <size_t... I>
void copy_construct(const tuple &other, std::index_sequence<I...>)
(new (addr<I>()) Elemsget<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;
;
Alternatively, you could use this function:
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);
Once again, this is undefined behavior (because of the nullptr
dereference and the reinterpret_cast
) but it will work as expected with every major compiler. The function cannot be constexpr
(even though member offset is a compile-time calculation).
This looks very good. Is the calculation in real_offset always correct? Even if the tuple does not have standard layout?
– maufl
Mar 11 at 9:23
@maufl The calculation insidereal_offset
is always correct and works for any type (not just tuples). However, it usesreinterpret_cast
so it's technically undefined behavior and it cannot be used in aconstexpr
context (even though it's a compile-time calculation). The calculation insideoffset_of
only works for standard layout types.std::tuple
is standard layout so it will work for all tuples. A standard layout type is still standard layout even if it contains non-standard layout members. I'd recommend usingoffset_of
because it's not undefined behavior.
– Kerndog73
Mar 11 at 9:43
Thanks. I checked but in gcc,std::tuple
does not have standard layout. Undefined behavior is what scares me, and why I did not pick an implementation yet.
– maufl
Mar 11 at 22:12
@maufl I just checked with clang and you're right. Damn!
– Kerndog73
Mar 11 at 22:21
@maufl Maybe you could find a way of implementing a tuple that is standard layout. One idea would be to store an array of bytes and use offset_of and placement new. That would be standard layout.
– Kerndog73
Mar 11 at 22:23
|
show 2 more comments
Calculating offsets is quite simple. Given a tuple with types T0, T1 ... TN. The offset of T0
is 0
(as long as you use alignas(T0)
on your char
array. The offset of T1
is the sizeof(T0)
rounded up to alignof(T1)
.
In general, the offset of TB
(which comes after TA
) is round_up(offset_of<TA>() + sizeof(TA), alignof(TB))
.
Calculating the offsets of elements in a std::tuple
could be done like this:
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;
Here's a test suite. As you can see from the first test, the alignment of elements is taken into account.
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);
I hardcoded the offsets in the above tests. The offsets are correct if the following tests succeed.
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
seems to store it's elements sequentially (without sorting them to optimize padding). That's proven by the following tests. I don't think the standard requires std::tuple
to be implemented this way so I don't think the following tests are guaranteed to succeed.
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)));
Now that I've gone to all of this effort, would that real_offset
function suit your needs?
This is a minimal implementation of a tuple that accesses a char[]
with offset_of
. This is undefined behavior though because of the reinterpret_cast
. Even though I'm constructing the object in the same bytes and accessing the object in the same bytes, it's still UB. See this answer for all the standardese. It will work on every compiler you can find but it's UB so just use it anyway. This tuple is standard layout (unlike std::tuple
). If the elements of your tuple are all trivially copyable, you can remove the copy and move constructors and replace them with 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>()) Elemsstd::move(get<I>(other)), ...);
template <size_t... I>
void copy_construct(const tuple &other, std::index_sequence<I...>)
(new (addr<I>()) Elemsget<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;
;
Alternatively, you could use this function:
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);
Once again, this is undefined behavior (because of the nullptr
dereference and the reinterpret_cast
) but it will work as expected with every major compiler. The function cannot be constexpr
(even though member offset is a compile-time calculation).
Calculating offsets is quite simple. Given a tuple with types T0, T1 ... TN. The offset of T0
is 0
(as long as you use alignas(T0)
on your char
array. The offset of T1
is the sizeof(T0)
rounded up to alignof(T1)
.
In general, the offset of TB
(which comes after TA
) is round_up(offset_of<TA>() + sizeof(TA), alignof(TB))
.
Calculating the offsets of elements in a std::tuple
could be done like this:
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;
Here's a test suite. As you can see from the first test, the alignment of elements is taken into account.
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);
I hardcoded the offsets in the above tests. The offsets are correct if the following tests succeed.
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
seems to store it's elements sequentially (without sorting them to optimize padding). That's proven by the following tests. I don't think the standard requires std::tuple
to be implemented this way so I don't think the following tests are guaranteed to succeed.
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)));
Now that I've gone to all of this effort, would that real_offset
function suit your needs?
This is a minimal implementation of a tuple that accesses a char[]
with offset_of
. This is undefined behavior though because of the reinterpret_cast
. Even though I'm constructing the object in the same bytes and accessing the object in the same bytes, it's still UB. See this answer for all the standardese. It will work on every compiler you can find but it's UB so just use it anyway. This tuple is standard layout (unlike std::tuple
). If the elements of your tuple are all trivially copyable, you can remove the copy and move constructors and replace them with 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>()) Elemsstd::move(get<I>(other)), ...);
template <size_t... I>
void copy_construct(const tuple &other, std::index_sequence<I...>)
(new (addr<I>()) Elemsget<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;
;
Alternatively, you could use this function:
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);
Once again, this is undefined behavior (because of the nullptr
dereference and the reinterpret_cast
) but it will work as expected with every major compiler. The function cannot be constexpr
(even though member offset is a compile-time calculation).
edited Mar 12 at 0:00
answered Mar 8 at 22:25
Kerndog73Kerndog73
9441027
9441027
This looks very good. Is the calculation in real_offset always correct? Even if the tuple does not have standard layout?
– maufl
Mar 11 at 9:23
@maufl The calculation insidereal_offset
is always correct and works for any type (not just tuples). However, it usesreinterpret_cast
so it's technically undefined behavior and it cannot be used in aconstexpr
context (even though it's a compile-time calculation). The calculation insideoffset_of
only works for standard layout types.std::tuple
is standard layout so it will work for all tuples. A standard layout type is still standard layout even if it contains non-standard layout members. I'd recommend usingoffset_of
because it's not undefined behavior.
– Kerndog73
Mar 11 at 9:43
Thanks. I checked but in gcc,std::tuple
does not have standard layout. Undefined behavior is what scares me, and why I did not pick an implementation yet.
– maufl
Mar 11 at 22:12
@maufl I just checked with clang and you're right. Damn!
– Kerndog73
Mar 11 at 22:21
@maufl Maybe you could find a way of implementing a tuple that is standard layout. One idea would be to store an array of bytes and use offset_of and placement new. That would be standard layout.
– Kerndog73
Mar 11 at 22:23
|
show 2 more comments
This looks very good. Is the calculation in real_offset always correct? Even if the tuple does not have standard layout?
– maufl
Mar 11 at 9:23
@maufl The calculation insidereal_offset
is always correct and works for any type (not just tuples). However, it usesreinterpret_cast
so it's technically undefined behavior and it cannot be used in aconstexpr
context (even though it's a compile-time calculation). The calculation insideoffset_of
only works for standard layout types.std::tuple
is standard layout so it will work for all tuples. A standard layout type is still standard layout even if it contains non-standard layout members. I'd recommend usingoffset_of
because it's not undefined behavior.
– Kerndog73
Mar 11 at 9:43
Thanks. I checked but in gcc,std::tuple
does not have standard layout. Undefined behavior is what scares me, and why I did not pick an implementation yet.
– maufl
Mar 11 at 22:12
@maufl I just checked with clang and you're right. Damn!
– Kerndog73
Mar 11 at 22:21
@maufl Maybe you could find a way of implementing a tuple that is standard layout. One idea would be to store an array of bytes and use offset_of and placement new. That would be standard layout.
– Kerndog73
Mar 11 at 22:23
This looks very good. Is the calculation in real_offset always correct? Even if the tuple does not have standard layout?
– maufl
Mar 11 at 9:23
This looks very good. Is the calculation in real_offset always correct? Even if the tuple does not have standard layout?
– maufl
Mar 11 at 9:23
@maufl The calculation inside
real_offset
is always correct and works for any type (not just tuples). However, it uses reinterpret_cast
so it's technically undefined behavior and it cannot be used in a constexpr
context (even though it's a compile-time calculation). The calculation inside offset_of
only works for standard layout types. std::tuple
is standard layout so it will work for all tuples. A standard layout type is still standard layout even if it contains non-standard layout members. I'd recommend using offset_of
because it's not undefined behavior.– Kerndog73
Mar 11 at 9:43
@maufl The calculation inside
real_offset
is always correct and works for any type (not just tuples). However, it uses reinterpret_cast
so it's technically undefined behavior and it cannot be used in a constexpr
context (even though it's a compile-time calculation). The calculation inside offset_of
only works for standard layout types. std::tuple
is standard layout so it will work for all tuples. A standard layout type is still standard layout even if it contains non-standard layout members. I'd recommend using offset_of
because it's not undefined behavior.– Kerndog73
Mar 11 at 9:43
Thanks. I checked but in gcc,
std::tuple
does not have standard layout. Undefined behavior is what scares me, and why I did not pick an implementation yet.– maufl
Mar 11 at 22:12
Thanks. I checked but in gcc,
std::tuple
does not have standard layout. Undefined behavior is what scares me, and why I did not pick an implementation yet.– maufl
Mar 11 at 22:12
@maufl I just checked with clang and you're right. Damn!
– Kerndog73
Mar 11 at 22:21
@maufl I just checked with clang and you're right. Damn!
– Kerndog73
Mar 11 at 22:21
@maufl Maybe you could find a way of implementing a tuple that is standard layout. One idea would be to store an array of bytes and use offset_of and placement new. That would be standard layout.
– Kerndog73
Mar 11 at 22:23
@maufl Maybe you could find a way of implementing a tuple that is standard layout. One idea would be to store an array of bytes and use offset_of and placement new. That would be standard layout.
– Kerndog73
Mar 11 at 22:23
|
show 2 more comments
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55071632%2fdetermine-member-offset-of-struct-or-tuple-in-template%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown