I would like to use a structured binding declaration to bring members of a struct into scope. I hope that the optimizer is able to remove the variables I don't actually use, but that's not my primary question. I got bitten by a destructor call I didn't expect:
#include <iostream>
using namespace std;
struct S {
int x = 0;
S() { cout << "S: " << this << endl; }
~S() { cout << "~S: " << this << endl; }
};
// Why does f() call ~S()?
void f(S const& s) {
auto [x] {s};
cout << "f: " << x << endl;
}
void g(S const& s) {
auto& [x] {s};
cout << "g: " << x << endl;
}
void h(S const& s) {
auto x = s.x;
cout << "h: " << x << endl;
}
int main(int, char**) {
S s;
f(s);
g(s);
h(s);
}
Running above program yields:
S: 0x7fff32c81778
f: 0
~S: 0x7fff32c81740
g: 0
h: 0
~S: 0x7fff32c81778
https://godbolt.org/z/cjeTnvx8G
The destructor call at the end of main()
is expected. But why does f()
call ~S()
?
I've read the documentation on cppreference but apparently I don't understand C++'s inner workings well enough to spot why this causes a destructor call.
I am wording this not as a language lawyer, but hope to give some intuition how structured bindings work.
When you read
auto [key, value] = foo();
replace for a moment the [key, value]
part by an invented name:
auto invented_name = foo();
Now it is obvious that the code holds onto an instance of the value returned by foo()
. Furthermore, if there are any reference specifiers or cv qualifiers, they apply to the invented name. I.e.,
const auto& [key, value] = foo();
becomes
const auto& invented_name = foo();
and ditto for &&
. Reference and cv qualifiers do not apply to the names key
and value
(but other rules achieve that they basically behave as if they were applied).
The names key
and value
now merely become aliases for the first and second structure members of invented_name
(or the return values of get<0>(invented_name)
and get<1>(invented_name)
).