1

Take the simple snippet:

void test(std::vector<int> && vec){
    auto const ptr = data(vec);
    auto new_vec {std::move(vec)};
    (void)*ptr; // UB or not?
};

Is using pointer achieved through applying data on the vector valid after moving the vector via the move constructor?

On a test project with MSVC-17.65 and /std:c++latest, It works. But is it just good luck or correct usage?

Red.Wave
  • 2,790
  • 11
  • 17
  • related/dupe: https://stackoverflow.com/questions/11021764/does-moving-a-vector-invalidate-iterators – NathanOliver Jul 21 '23 at 16:08
  • https://en.cppreference.com/w/cpp/container/vector/vector, after move the moved from vector should be empty. See section 8). In any case after the move the original object (your vector) is in **a valid but unspecified state**. So summarizing no your code is not correct, the moved from vector should no longer be used (except for its destructor) – Pepijn Kramer Jul 21 '23 at 16:10
  • 7
    What is `data(vec)`? – Nathan Pierson Jul 21 '23 at 16:15
  • its std::data https://en.cppreference.com/w/cpp/iterator/data – marcinj Jul 21 '23 at 16:16
  • 2
    If that's the case I'd suggest the question would be better using `std::data`, even though ADL makes it not necessary from the compiler's perspective. – Nathan Pierson Jul 21 '23 at 16:17
  • 5
    @PepijnKramer: But look further down on that page, under Notes: "After container move construction (overload (8)), references, pointers, and iterators (other than the end iterator) to other remain valid, but refer to elements that are now in *this. The current standard makes this guarantee via the blanket statement in [container.rev.reqmts]/17, and a more direct guarantee is under consideration via LWG 2321." They sound pretty positive, but to me the actual language of the current standard is more dicey. – Nate Eldredge Jul 21 '23 at 16:57
  • As LWG 2321 points out, `std::swap` does make such a guarantee explicitly. – Nate Eldredge Jul 21 '23 at 16:58
  • @PepijnKramer *"vector should be empty ... valid but unspecified state"* Aren't you contradicting yourself? (If we ignore stateful allocators.) – HolyBlackCat Jul 21 '23 at 17:17
  • 1
    Most standard C++ library objects are in a valid-but-unspecified state after a move. Some of them — like `std::unique_ptr` or `std::vector` have **additional guarantees**. Howard Hinnant had mentioned (here on SO) that for your own user-defined types, they can have whatever post-move guarantees that one codes up for their contract (such as valid&unspecified, or freshly initialized, or completely intact, or something else). – Eljay Jul 21 '23 at 17:44
  • 1
    Thanks for comments; but the state of original object is not the concern here. The state of references created to elements before move is what I ask about. Even iterators are not in question, because I can imagine senarios where they may depend on the identity of the original container. But a pointer to elements is only dependent on the allocated resource, and I am not using fancy pointers in the allocator. So, if the move construction is guaranteed to steel vector's resource, code is well-formed, otherwise ill-formed. I find @NateEldredge most relevant. So, Please kindly reply in that regard. – Red.Wave Jul 21 '23 at 17:55
  • 2
    The summary as it appears to me is that there is some debate whether the current wording of the standard would guarantee it. However, (1) it was probably intended to work; (2) there is a proposal to say explicitly that it must work; (3) you can rewrite using `std::swap` if you want to be more certain. – Nate Eldredge Jul 21 '23 at 21:12
  • By the way, as long as we are being language lawyers, I don't think "well-formed" / "ill-formed" is the terminology you want here. That refers only to syntax and *diagnosable* semantics; basically, errors that are guaranteed detectable statically. I think your program is *well-formed* no matter what, but if `std::vector` doesn't "steal" the resource as you put it, then it might have *undefined behavior*. – Nate Eldredge Jul 21 '23 at 21:17
  • In other words, assuming the construct is illegal, there can be instances of it which an implementation could not diagnose at compile time, because of the halting problem. So it is not diagnosable. – Nate Eldredge Jul 21 '23 at 21:21
  • 1
    And just FYI, someone already made an edit, but: the English word meaning "take something away" is *steal*. The spelling *steel* is for the metal. – Nate Eldredge Jul 21 '23 at 21:26
  • 1
    Whatever the intent behind the question (yes, I saw the LL tag), and whatever the standard might say, this looks to me like a classic 'use after free' bug waiting to happen. Remember when, back in the day, people used to do that deliberately as a convenient shortcut? Not looking so clever now, are they? – Paul Sanders Jul 21 '23 at 21:55
  • @PaulSanders that is the question: is it use after free? If the new instance does steal the resource, it is not use after free; otherwise it is. But if the behavior is not guaranteed by std, whatever the result it is UB. – Red.Wave Jul 23 '23 at 09:34
  • @PaulSanders the details of usage are not provided; but it is guaranteed that `ptr` is `const` and won't be accessed after `new_vec`. So if `ptr` actually refers to `new_vec`, then it is ok. And yes, I want to init `ptr` prior to `new_vec`. – Red.Wave Jul 23 '23 at 09:42
  • @NateEldredge does `swap` provide the guarntee to keep `data` at same address? That looks an interesting work around. Usually swap and move implementation are interdependent: either swap through 3x move, or move through swap + delegated default ctor; the later case means source object will be in default(null) state. Sorry about bad spelling and thanks for terminology hint. – Red.Wave Jul 23 '23 at 09:51
  • `vector`'s move constructor is required to have constant complexity; I don't see how else that could be achieved so I think your guarantee is safely implied. – ildjarn Jul 23 '23 at 12:57
  • 1
    @Red.Wave: Yes, for `swap`, this guarantee is made quite clearly by [container.requirements.general] p9: "Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap." And p11.6: "no swap() function invalidates any references, pointers, or iterators referring to the elements of the containers being swapped." The LWG 2321 proposal is to add something similar for the move constructor, more or less to say that it operates as if by swap. – Nate Eldredge Jul 23 '23 at 16:04
  • 1
    @ildjarn: An implementation could have ways to move an object to a new address in O(1) time, without needing an O(n) copy; e.g. by remapping virtual memory. Unlikely in practice, but this is a [tag:language-lawyer] question so we have to consider all possibilities. – Nate Eldredge Jul 23 '23 at 16:06
  • So until the adoption of LWG2321, I should stick with the swap workaround. I wish idiomatic ways of defining the interdependency between move and swap were popularized. Before C++11, the copy-swap idiom was the primary implementation of rule of 3; not always the best choice, but always functional. I just craft my idioms(maybe wrong ones): swap=3x move, move ctor=swap+default ctor... – Red.Wave Jul 23 '23 at 19:05

0 Answers0