1

I tested the following code, but the results of gcc/clang and MSVC seem different.

Is this undefined behavior or an MSVC bug? I thought assigning a value to a C++ std container is a deep copy, according to SO answers such as this and this. So I thought this is a legal code.

Please help me if I missed some basic concepts of C++.

#include <iostream>
#include <vector>
#include <map>
#include <tuple> 
struct S {
    std::vector<int> v; 
    std::tuple<std::vector<int>&> tup{v};
}; 
int main() {
    std::map<int, std::map<int, S>> m_n_s; 
    for (int j = 0; j < 1; ++j) {
        std::map<int, S> n_s; 
        for (int i = 0; i < 1; ++i) {
            std::vector<int> v2{33,44};
            S s;
            s.v = v2; 
            n_s[8] = s; 
        }
        m_n_s[9] = n_s; 
    }
    S & s = m_n_s.at(9).at(8);
    std::cout << s.v.size() << '\n';        // size: 2
    std::cout << s.v.at(0) << '\n';         // 33 
    std::cout << std::get<0>(s.tup).size(); // size: 2(gcc,clang) 0(MSVC)
}
starriet
  • 2,565
  • 22
  • 23
  • 6
    `-fsanitize=address` is your friend. Hint: when you copy-construct an `S`, which vector does the copy's `tup` refer to? – T.C. Apr 14 '23 at 04:46
  • @T.C. Thank you. As you advised, I tried the `-fsanitize=address` but I think it would take a while for me to understand the message(I should learn more :) But let me answer your quiz. I thought the copied `s`'s `tup` refers to the vector `v` of the copied `s`, since it's a deep copy and also the 'reference' in C++ is kind of an "alias" for another variable(although most implementations use pointer I think). But I guess I didn't get it right, so please let me know what I misunderstood. – starriet Apr 14 '23 at 06:36
  • Aside: you don't need that for loop. `m_n_s[9][8].v = {33,44};` does the same job – Caleth Apr 14 '23 at 12:43
  • @Caleth Right, but that code doesn't copy the reference so there is no original object to refer to (no dangling reference). I mean, it doesn't make the same problem as I described, right? :) – starriet Apr 14 '23 at 13:13

2 Answers2

5

tup is initialised when S is first default constructed. If you then copy S to another object the address of v will be different but tup will still be a reference to v in the original object. Once the original object is destroyed the reference is dangling and using it is undefined behaviour.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • I might have understood your answer easily if the `tup` contained a pointer rather than a reference, but I'm a bit confused because it's a reference, not a pointer. So... in general, if we copy a reference, it still refers to the original variable, right? (I think now I'm getting close to understanding) – starriet Apr 14 '23 at 06:50
  • 1
    A reference is basically the same as a pointer but with a few restrictions (they can't be null and they can't be reassigned) – Alan Birtles Apr 14 '23 at 06:54
  • Change v to std::vector*, and tup to std::tuple&> tup{*v}. However you still may end up having a dangling reference while reallocating v. – user1079475 Apr 14 '23 at 09:22
  • @user1079475 I tried as you mentioned, but it's not compiled. And I don't understand how it works. Could you elaborate on that? – starriet Apr 14 '23 at 11:54
  • 1
    @starriet I'm guessing they meant something like this https://godbolt.org/z/zr3zeqdfo it fixes your problem but adds a memory leak – Alan Birtles Apr 14 '23 at 12:18
2

Because you haven't defined any constructors for S, they are defined by the implementation. The copy constructor does a memberwise copy, which initialises the reference, rather than using the default initialiser {v}. Once the innermost scope in your loops ends, the reference dangles.

You can fix that by defining copy (and move) yourself:

struct S {
    std::vector<int> v; 
    std::tuple<std::vector<int>&> tup{v};
    S() = default;
    S(const S & s) : v(s.v) {}
    S(S && s) : v(std::move(s.v)) {}
    S& operator=(const S & s) { v = s.v; }
    S& operator=(S && s) { std::swap(v, s.v); }
};

See it on godbolt

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Oh, thanks! What if there are many members, such as `v_1`, `v_2`, ... `v_N`? I was trying to solve [this](https://stackoverflow.com/q/76014659/10027592) problem actually. – starriet Apr 14 '23 at 13:15
  • 1
    @starriet you'd copy each member apart from the tuple. Not having reference data members is better, so use that answer – Caleth Apr 14 '23 at 13:22