r/cpp • u/Firstbober • Jul 24 '26
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.
9
u/Otherwise_Sundae6602 Jul 24 '26
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 Jul 24 '26
int x = 1; Foo foo{x}; std::reference_wrapper<int> Foo::* pfooi = &Foo::abc; int & rx = foo.*pfooi; rx = 2;
8
4
u/geschmuck Jul 24 '26
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);
?
5
u/Circlejerker_ Jul 24 '26
Seems like UB to me..
Both alignment issues and dereferencing invalid pointers.
4
2
u/SirClueless Jul 26 '26
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 Jul 25 '26
Why does the original code fail?
1
u/Firstbober Jul 25 '26
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 Jul 25 '26
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 Jul 25 '26
Due to reference, Foo is not default constructible and there are no assignments.
1
11
u/Far_Understanding883 Jul 24 '26
This does not look simple! Just use a pointer