There's an error for this case:
const int& foo() {
const int x = 0;
return x;
}
and even
const int& foo() {
const std::pair<int,int> x = {0,0};
return x.first;
}
but not this:
const int& foo() {
const std::array<int,1> x = {0};
return x[0];
}
and (less surprisingly) not this:
const int& foo() {
const std::vector<int> x = {0};
return x[0];
}
Particularly in the std::vector
case, I get that this warning would be pretty tricky, since it's not obvious to the compiler that the const int&
returned by std::vector<int>::operator[](size_t) const
is a reference into the temporary. I'm actually a little surprised that std::array
doesn't fail, though, since this similar case does give me an error:
struct X {
int x[0];
};
const int& foo() {
X x;
return x.x[0];
}
Do any of the popular compilers have a warning/error that can catch these cases? I could imagine a conservative version that would warn about returning a reference that came from a member-function call on a temporary.
I tripped over this with something like the following, in which I inlined a chained series of calls, but because C++ lets you assign locals to const&
, the verbose version works while the superficially-identical version deletes the temporary right away, leaving a dangling reference:
#include <iostream>
struct A {
int x = 1234;
A() { std::cout << "A::A " << this << std::endl; }
~A() { x = -1; std::cout << "A::~A " << this << std::endl; }
const int& get() const { return x; }
};
struct C {
C() { std::cout << "C::C " << this << std::endl; }
~C() { std::cout << "C::~C " << this << std::endl; }
A a() { return A(); }
};
int foo() {
C c;
const auto& a = c.a();
const auto& x = a.get();
std::cout << "c.a(); a.get() returning at " << &x << std::endl;
return x;
}
int bar() {
C c;
const int& x = c.a().get();
std::cout << "c.a().get() returning at " << &x << std::endl;
return x;
}
int main() {
std::cout << foo() << std::endl;
std::cout << bar() << std::endl;
}
That outputs
C::C 0x7ffeeef2cb68 A::A 0x7ffeeef2cb58 c.a(); a.get() returning at 0x7ffeeef2cb58 A::~A 0x7ffeeef2cb58 C::~C 0x7ffeeef2cb68 1234 C::C 0x7ffeeef2cb68 A::A 0x7ffeeef2cb58 A::~A 0x7ffeeef2cb58 c.a().get() returning at 0x7ffeeef2cb58 C::~C 0x7ffeeef2cb68 -1
Using a value whose life has ended is undefined behavior, see [basic.life]/6.1. The standard does not require the compiler to output any diagnostics for UB.
So the diagnostics you're seeing are just a courtesy of the compiler. It's nice to see you're getting some of those, but they are certainly far from watertight as you have noticed.
And yes, lifetime extension isn't chainable. That makes it very dangerous and unreliable.
You can try Clang's Address Sanitizer (ASAN).
In fact ASAN seems to be catching your issue (
-fsanitize-address-use-after-scope
):