c++c++17structured-bindings

Why does structured binding declaration call destructor?


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.


Solution

  • 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)).