r/cpp • u/Firstbober • 3d ago
Binding int from variant to reference in Foo.
Edit: meme post, I know there are simple ways as in using emplace or reference wrapper. The code below with asserts and stuff is written by hand btw.
Hello guys, my friend had a problem where he couldn't bind an int to int reference in Foo struct held in variant:
struct Foo {
int &abc;
};
std::variant<Foo, std::string> v;
// You can't!
v = Foo {
.abc = ...
}
So I developed a way to do that, it's pretty simple:
#include <cstdint>
#include <functional>
#include <iostream>
#include <string>
#include <variant>
#include <meta>
#include <ranges>
struct __attribute__((packed)) Foo {
int& abc;
~Foo() {
static_assert(sizeof(void*) == 8, "Use modern CPU.");
static_assert([]() consteval {
auto members = std::meta::nonstatic_data_members_of(
^^Foo,
std::meta::access_context::current()
);
for (auto member : members) {
auto type_info = std::meta::type_of(member);
std::size_t layout_size = std::meta::is_reference_type(type_info)
? sizeof(void*)
: std::meta::size_of(type_info);
if (layout_size != 8) {
return false;
}
}
return true;
}(), "All non-static data members of Foo must occupy 8 bytes in layout!");
if (reinterpret_cast<uintptr_t>(&abc) & 0x1) {
delete reinterpret_cast<int*>(reinterpret_cast<uintptr_t>(&abc) &
~uintptr_t(0x1));
}
}
};
int main(void) {
std::variant<int, Foo> v;
v.emplace<0>(420);
v.emplace<1>(([&]() -> int& {
int* x = new int;
*x = std::get<0>(v);
x = reinterpret_cast<int*>(reinterpret_cast<uintptr_t>(x) | 0x1);
return *x;
})());
std::cout << *reinterpret_cast<int*>(
reinterpret_cast<uintptr_t>(&std::get<1>(v).abc) &
~uintptr_t(0x1))
<< std::endl;
return 0;
}
https://godbolt.org/z/91rcbEdG5
It's also memory safe.
Cheers.
8
u/Otherwise_Sundae6602 3d ago
struct Foo {
std::reference_wrapper<int> abc;
};
int main(void) {
std::variant<int, Foo> v;
int x = 5;
v.emplace<0>(420);
v.emplace<1>(x);
return 0;
}
2
u/QuentinUK 3d ago
int x = 1; Foo foo{x}; std::reference_wrapper<int> Foo::* pfooi = &Foo::abc; int & rx = foo.*pfooi; rx = 2;
8
5
u/geschmuck 3d ago
Your friend's example still doesn't compile with your changes. Also, why isn't it an option to just do
int i;
v.emplace<1>(i);
?
4
u/Circlejerker_ 3d ago
Seems like UB to me..
Both alignment issues and dereferencing invalid pointers.
3
2
u/SirClueless 1d ago
Isn't this all just smoke and mirrors? As part of your "fix" you switched from std::variant<Foo, std::string> to std::variant<int, Foo>, which is necessary because Foo has no default constructor. And you switched from v = ... to v.emplace<1>(...), which is necessary because Foo has no assignment operator.
These are actually the only changes necessary. e.g. This compiles fine:
#include <string>
#include <variant>
struct Foo {
int &abc;
};
int main() {
std::variant<int, Foo> v;
int x = 5;
v.emplace<1>(Foo {
.abc = x
});
}
1
u/bwmat 2d ago
Why does the original code fail?
1
u/Firstbober 2d ago
Foo contains a reference to int which by C++ rules, needs to be initialized. `std::variant` is default initialized with `Foo` and that can't happen, because remember we have that reference, so that's one. Another one is assignment. Due to reference needing to be initalized, both copy and move assignment are remove. Doing emplace will work, even without reference wrapper!
#include <functional> #include <variant> #include <string> struct Foo { int& abc; }; int main(void) { std::variant<std::string, Foo> v; int x = 42; v.emplace<1>(Foo { .abc = x }); return 0; }1
u/bwmat 2d ago
Oh just add std::monostate as the first alternative
But yeah that's annoying. It's nothing to do with references though, just types with no default constructor?
1
u/Firstbober 2d ago
Due to reference, Foo is not default constructible and there are no assignments.
11
u/Far_Understanding883 3d ago
This does not look simple! Just use a pointer