1

What happens to a std::reference_wrapper if the reference used to create it goes out of scope? Can it still still provide access to the underlying object (which would still exist), or would it be dangling and attempts to use it would result in UB?

I was experimenting with this code, and it compiled and run. Was I just lucky, or is this the expected behavior?

#include <iostream>
using namespace std;


struct mytype {
    int stuff;
};

mytype m;

bool myfunc(mytype& my) { return my.stuff > 0; }

void fill(std::reference_wrapper<mytype>& rwrap) {
    std::cout << "entering fill" << std::endl;
    std::cout << "myfunc(rwrap) : " << myfunc(rwrap) << std::endl;
    rwrap = std::ref(m);
    std::cout << "myfunc(rwrap): " << myfunc(rwrap) << std::endl;
    std::cout << "fill done" << std::endl;
}

int main() {
    mytype a;
    a.stuff = 7;
    std::reference_wrapper<mytype> r = a;

    std::cout << "a.stuff: " << a.stuff << std::endl;
    std::cout << "r.stuff(pre):  " << r.get().stuff << std::endl;
    std::cout << "myfunc(r):" << myfunc(r) << std::endl;

    fill(r);

    std::cout << "r.stuff(post): " << r.get().stuff << std::endl;
    std::cout << "myfunc 4: " << myfunc(r) << std::endl;

    std::cout << "a.stuff: " << a.stuff << std::endl;
}

output:

a.stuff: 7
r.stuff(pre):  7
myfunc(r):1
entering fill
myfunc(rwrap) : 1
myfunc(rwrap): 0
fill done
r.stuff(post): 0
myfunc 4: 0
a.stuff: 7

Code at compiler explorer: https://godbolt.org/z/4eTnbvqjT

EDIT: This worked too. Just made it to demo to myself how it all "collapses".

void fill(std::reference_wrapper<mytype>& rwrap) {
    mytype& mref1 = m;
    mytype& mref2 = mref1;
    mytype& mref3 = mref2;
    mytype& mref4 = mref3;
    std::cout << "entering fill" << std::endl;
    std::cout << "myfunc(rwrap) : " << myfunc(rwrap) << std::endl;
    rwrap = std::ref(mref4);
    std::cout << "myfunc(rwrap): " << myfunc(rwrap) << std::endl;
    std::cout << "fill done" << std::endl;
}
Chris
  • 13
  • 1
  • 3
  • Sorry - I had made a mistake in my initial post: I had m inside the fill function, but now moved it to global scope: I just wanted to investigate the case of the reference going out of scope, not the original object. – Chris Feb 26 '23 at 14:46

1 Answers1

0
rwrap = std::ref(m);

This rebinds rwrap, a.k.a. r, as a reference to m.

m goes out of scope and gets destroyed when fill returns. Subsequence usage of r results in undefined behavior.

if the reference used to create it goes out of scope?

It is not std::ref(m) that goes out of scope and gets destroyed here. That's fine and dandy. Once the assignment rebinds wrap, the reference it gets rebound from is something that nobody cares about any more.

It's not that the reference that goes out of scope that results in undefined behavior, here, but the underlying objects going poof when fill returns.

You then changed the code example so that m is now a global object, instead of declared local to the function, so it now continues to exist, and this is now well-defined. The reasoning is identical, the fact that the reference wrapper was initialized from a different reference wrapper is, still, immaterial.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • I am sorry for the confusion: I edited my code to have the question correctly asked - I had to move m out of the fill function into global scope for that. So - if I understand correctly: reference_wrapper stays valid, as long as the original object is alive? – Chris Feb 26 '23 at 14:47
  • Well, I have good news for you, my original answer also explains the alternate scenario as well. Since `m` still remains in scope, and `r` refers to it, this is now well-defined. So, the end result is different, but the explanation covers that as well. – Sam Varshavchik Feb 26 '23 at 14:49
  • Thanks a lot - I guess that closes the case. – Chris Feb 26 '23 at 16:36