Why does this compile/why doesn't the compiler warn against it? It seems obvious that str shouldn't be referenced (in the fn function call) before its initialization. Does the C++ standard not prevent this? And if so what's the purpose of referencing a variable before it's initialized?
It doesn't warn on
std::string str = fn(str);
because that is a legitimate and well-defined use. You could for example write this and have fn
store a pointer or reference to str
somewhere for later use.
Compilers are not generally performing any deep analysis for the purpose of warnings. Therefore I wouldn't expect them to check whether the function fn
is actually using the reference in a valid manner with regards to lifetime of str
.
If you want such an analysis use a static analyzer or similar tool, e.g. clang-tidy, clang-analyzer or GCC's -fanalyzer
(although that seems to be buggy with C++ at the moment).
This code compiles fine on all compilers I tried (GCC, Clang, and MSVC, as shown here https://godbolt.org/z/jExWYW6b9), and segfaults on all of them on Godbolt. However, I can compile this code and execute it without segfault with Apple clang version 13.1.6 (clang-1316.0.21.2.5) on a M1 mac (tested under arm64 and x86_64 with Rosetta 2, and it prints oh no! to stdout). Why does this work with Apple Clang and doesn't segfault? I can't seem to reproduce this on Godbolt.
While forming a reference or pointer to str
outside its lifetime is allowed, calling a member function on or accessing a member of a class type object before its lifetime begins, meaning before its initialization is complete, as you are doing with
str = "oh no!";
causes undefined behavior. (Exceptions apply if the call/access happens through a constructor this
pointer during construction.) One possible outcome of undefined behavior is that it seems to work and another is that it segfaults, as well as many other possibilities.
Even if you remove that line, you are still calling the copy constructor for str
with a reference to itself as argument. I am not actually sure at the moment whether the standard implicitly or explicitly disallows such a use for the standard library types. I guess implicitly the specification of the std::string
constructor does forbid it. In any case I expect that it is intended to not be allowed, in which case this would still be a library precondition violation and undefined behavior.