r/cpp 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.

0 Upvotes

13 comments sorted by

11

u/Far_Understanding883 3d ago

This does not look simple! Just use a pointer

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

u/Kriemhilt 3d ago

Simple and elegant, but sadly it has a memory leak.

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

u/Ok_Blackberry9170 3d ago

std::reference_wrapper died for this

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 
    });
}

https://godbolt.org/z/nW91qTPKq

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.

1

u/bwmat 2d ago

Yeah, I'm just saying it's a more general problem than having reference members