3

Fun with lifetime-extended results of assignment

 1 year ago
source link: https://quuxplusone.github.io/blog/2022/07/09/lifetime-extended-assignments/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Fun with lifetime-extended results of assignment

Previously on this blog: “Fun with conversion-operator lookup” (2021-01-13).

Here’s another simple C++ program that gives different output on three of the four mainstream compilers (Godbolt):

#include <stdio.h>
char messages[][23] = {
  "This is Clang (or EDG)",
  "This is GCC",
  "This is MSVC",
};
int count;
struct X { ~X() { ++count; } };

int main() {
  {
    X& a = (X() = X());
    count = 0;
  }
  puts(messages[count]);
}

As far as I can tell, Clang and EDG are correct; the other two are buggy.


We create two X objects in this program: the temporary on the left-hand side of the assignment operator, and the temporary on the right-hand side. MSVC seems to be doing a sort of “lifetime extension” of both objects all the way to the end of a’s scope. [class.temporary]/6 doesn’t seem to offer any textual justification for extending the left-hand operand, let alone the right-hand operand.

GCC seems to be extending the lifetime of the left-hand X() but not the right-hand one. (We can tell by instrumenting the constructors: Godbolt.) But here’s the weird part: GCC does this lifetime extension only when struct X has no named fields! That is, GCC will lifetime-extend the left-hand operand of an assignment when its type is any of these:

struct alignas(4) X { ~X(); };
struct X { int :0; ~X(); };
struct X { int :16; ~X(); };

but not when its type is either of these:

struct X { int i:1; ~X(); };
struct X { char c; ~X(); };

Incidentally, MSVC is the only vendor that allows struct X { int :16; } to qualify as std::is_empty. I think MSVC is correct (and Clang/GCC are wrong): such an X certainly has no non-static data members, since unnamed bit-fields are not members at all.

Finally, all this wackiness manifests only when X::operator= is defaulted (either implicitly, as shown here, or explicitly via =default). If you provide your own user-defined

X& operator=(X&&) { return *this; }

then the divergence vanishes: all vendors agree that there shouldn’t be any lifetime extension in that case.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK