I'm trying to iterate through an object's member std::vectors, using a member std::tuple that contains the references of the member vectors.
A simple example would be like this:
#include <iostream>
#include <vector>
#include <tuple>
struct S {
    std::vector<int> a;
    std::vector<double> b;
    std::tuple<std::vector<int>&, std::vector<double>&> tup{a, b};
};
int main() {
    S s; 
    s.a = {2, 3};
    s.b = {5.1, 6.1};
    std::apply([&](auto&... v){
        ((std::cout << v.size()), ...);
    }, s.tup);
}
This works, but the problem is that if I assign the object s to an std container, the references tup is holding can be dangling references. Such as:
    std::map<int, S> myMap;
    myMap[3] = s; // s.a and s.b has been copied to different addresses.
    auto& v = std::get<0>(myMap.at(3).tup); // -> still refers to the previous s.a, not the copied one.
Is there any decent way to solve this problem? I want the references to refer to the newly copied members, not the original ones so that the member vectors of the new object can be iterated through using the member tuple.
(This question is being written after asking this question.)
The key problem is that references cannot be re-seated. So when you copy/move an S, you cannot make the references inside the tuple point at the ones inside the new object.
The easiest way is to just make a member function and not have a data member:
struct S {
    std::vector<int> a;
    std::vector<double> b;
    auto tup() {
        return std::tie(a,b);
    }
};
If you really want to have it as member data, you can instead use raw pointers. Having non-owning raw pointers is totally fine. This way, you can write custom copy/move operations which fix up the pointers:
struct S {
    std::vector<int> a;
    std::vector<double> b;
    std::tuple<std::vector<int>*, std::vector<double>*> tup{&a, &b};
    S() = default;
    S(const S& other) : a(other.a), b(other.b), tup(&a, &b) {}
    S(S&& other) : a(std::move(other.a)), b(std::move(other.b)), tup(&a, &b) {}
    S& operator=(const S& other) { a = other.a; b = other.b; tup = std::make_tuple(&a, &b); }
    S& operator=(S&& other) { a = std::move(other.a); b = std::move(other.b); tup = std::make_tuple(&a, &b); }
};
If you really want them to be accessible as references for usability purposes, you could make tup a private member and write a member function which returns a tuple of references.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With