I was experimenting on wandbox in hopes of finding compiler warnings that would help with inadvertent dangling references in lambdas. I had this example which misbehaves in multiple ways:
std::array<std::function<const int *(void)>,N> createFunctions()
{
std::array<std::function<const int *(void)>,N> fns;
for ( int i = 0 ; i < N ; i++ ) {
std::cout << &i << " ";
fns[i] = [&]() {
return &i;
};
}
std::cout << "\n";
return fns;
}
This stuffs a set of lambdas into an array of functions. Each lambda returns a pointer to a reference to a captured variable, i
. Should be trouble all around.
My driver routine prints out the pointer, and dereferences it as well.
int main()
{
auto fns = createFunctions();
for (int j = 0 ; j < N ; j++ ) {
if (j != 0)
std::cout << ", ";
std::cout << fns[j]() << ": " << *fns[j]();
}
std::cout << "\n";
return 0;
}
If this lambda was modified to pass i
by copy, you'd get output like this - four pointers, and four pointers with unique values:
0x7ffc80e65358 0x7ffc80e65358 0x7ffc80e65358 0x7ffc80e65358
0x7ffc80e65380: 0, 0x7ffc80e653a0: 1, 0x7ffc80e653c0: 2, 0x7ffc80e653e0: 3
When it is written in error, taking a reference that passes silently out of scope, it miraculously runs without faulting, but clearly shows the error of its ways
0x7ffeebdfe9f4 0x7ffeebdfe9f4 0x7ffeebdfe9f4 0x7ffeebdfe9f4
0x7ffeebdfe9f4: 32766, 0x7ffeebdfe9f4: 32766, 0x7ffeebdfe9f4: 32766, 0x7ffeebdfe9f4: 32766
All four pointers are identical, and the value they are pointing to is bogus.
This is the case for all versions of g++, and all versions of clang++ up to 8.x. But clang 9.0 miraculously deals with it in a magical way:
0x7ffd193bfa30 0x7ffd193bfa30 0x7ffd193bfa30 0x7ffd193bfa30
0x7ffd193bfa30: 0, 0x7ffd193bfa30: 1, 0x7ffd193bfa30: 2, 0x7ffd193bfa30: 3
The part that is really interesting is that the pointer to the reference has the same value in all four lambdas - but dereferencing them returns four different values. I have tried to come up with a good explanation for how this can be, but I'm stumped.
I'm guessing that it is an intentional optimization in which the compiler has deduced what I want to do, and makes it happen. Since using a dangling reference falls into that "undefined behavior" category, the compiler is free to do what it likes. But is this the case?
If the compiler is smart enough to figure that out, it seems like it would be smart enough to issue a warning, but I don't get that.