How To Determine The Offset Of An Element Of A Tuple At Compile Time?
I need to determine the offset of a certain indexed element of a tuple at compile time.
I tried this function, copied from https://stackoverflow.com/a/55071840/225186 (near the end),
template <std::size_t I, typename Tuple>
constexpr std::ptrdiff_t element_offset() {
Tuple p;
return
(char*)(&std::get<I>(*static_cast<Tuple *>(&p)))
- (char*)(static_cast<Tuple*>(&p))
;
}
including variants in which I eliminate p
and replace &p
by nullptr
.
This function seems to work well at runtime but I cannot evaluate it at compile time.
https://godbolt.org/z/MzGxfT1cc
int main() {
using Tuple = std::tuple<int, double, int, char, short, long double>;
constexpr std::size_t index = 3;
constexpr std::ptrdiff_t offset = element_offset<index, Tuple>(); // ERROR HERE, cannot evaluate constexpr context
Tuple t;
assert(( reinterpret_cast<char*>(&t) + offset == reinterpret_cast<char*>(&std::get<index>(t)) )); // OK, when compiles (without "constexpr" offset)
}
I understand this is probably because the reinterpret_cast
s cannot be done at compile time.
But so far it is basically the only function that proved to work (at runtime).
Is there a way to rewrite this function in a way that can be evaluated at compile type?
I also tried these approached list at the beginning of https://stackoverflow.com/a/55071840/225186, but they all give garbage results (at least in GCC) because they assume a certain ordering of the tuple elements and the offset are calculated by "walking" index by index and aligning bytes.
Answer
You can use this:
template <std::size_t I, typename Tuple>
constexpr std::size_t element_offset() {
using element_t = std::tuple_element_t<I, Tuple>;
static_assert(!std::is_reference_v<element_t>);
union {
char a[sizeof(Tuple)];
Tuple t{};
};
auto* p = std::addressof(std::get<I>(t));
t.~Tuple();
std::size_t off = 0;
for (std::size_t i = 0;; ++i) {
if (static_cast<void*>(a + i) == p) return i;
}
}
Which avoids having to reinterpret_cast to a char pointer, and shouldn't have any undefined behaviour.
You can also make this work with tuples that can't be default constructed in a constant expression by not initializing the tuple:
template <std::size_t I, typename Tuple>
constexpr std::size_t element_offset() {
using element_t = std::tuple_element_t<I, Tuple>;
static_assert(!std::is_reference_v<element_t>);
union u {
constexpr u() : a{} {} // GCC bug needs a constructor definition
char a[sizeof(Tuple)]{};
Tuple t;
} x;
auto* p = std::addressof(std::get<I>(x.t));
std::size_t off = 0;
for (std::size_t i = 0;; ++i) {
if (static_cast<void*>(x.a + i) == p) return i;
}
}
While this works in gcc, clang, and msvc today, it might not in the future.
Related Questions
- → Comparing two large files are taking over four hours
- → Setting JSON node name to variable value
- → Compiling GLUT using Emscripten
- → Evaluate check box from a scanned image in node.js
- → Find an easy web server framework for mobile game
- → my https C++ code doesn't work on some sites (binance)
- → Error while opening pivx wallet on ubuntu
- → Why sending a POST by AJAX is interpreted by the HTTP Server as OPTIONS and sending by CURL is effectively a PUT?
- → Python reading in one line multiple types for a calculator
- → How do I properly pass an argument to a function
- → Accessing Websql database with Qt
- → Using Mysql C API for c++ codes
- → How do I set constants at run-time in a c++ header file, imported through Cython?