← 문제로

33. 구조화된 바인딩(structured bindings)의 내부 동작

난이도 중
내 리뷰 · C++
해설 · C++

해설 — 구조화된 바인딩(structured bindings)의 내부 동작

난이도: 중

요약

출력은 A p.x=99 p.y=20, B p.x=99 cx=-1, C arr={1,200,3}, D ti=7 td=3.5. auto [a,b] = e 는 이름 a,b를 만드는 게 아니라, **숨은 객체 e를 하나 만들고 a,b는 그 하위 객체(멤버/배열원소/get 결과)의 별명(alias)**이다. auto&면 숨은 객체가 원본을 참조하므로 바인딩 수정이 원본에 반영되고, auto(값)면 숨은 객체가 복사본이라 원본과 무관하다.

핵심 개념

구조화된 바인딩은 컴파일러가 대략 다음처럼 확장한다:

auto& [rx, ry] = p;
// ↓ 개념적 확장
auto& __e = p;                 // 숨은 이름 __e (여기선 참조)
// rx 는 __e.x 의 별명, ry 는 __e.y 의 별명
  • rx, ry참조 타입이 아니다(타입 질의 시 int). 하지만 언어가 "이 이름은 __e의 특정 하위 객체를 가리킨다"고 규정해, 접근하면 항상 __e의 그 하위 객체를 읽고 쓴다. 그래서 참조처럼 동작한다.

단계별 내부 동작

  1. (A) auto& [rx,ry]=p: __ep를 참조. rx = 99p.x를 바꾼다 → p.x=99, p.y=20.
  2. (B) auto [cx,cy]=p: __ep복사본. cx=-1은 복사본만 바꾼다 → 원본 p.x는 (A)에서 바뀐 99 그대로, cx=-1.
  3. (C) 배열 auto& [a0,a1,a2]=arr: 배열도 각 원소의 별명으로 분해된다(get이 아니라 원소 직접). a1=200arr[1]을 바꿈 → {1,200,3}.
  4. (D) tuple auto [ti,td]=t: tuple 은 std::tuple_size/std::tuple_element/get<i> 프로토콜로 분해된다. 값 복사라 ti=7, td=3.5(원본 무관).

세 가지 분해 경로

컴파일러는 대상 타입에 따라 다른 규칙을 쓴다:

  • 배열: 각 원소가 하나의 바인딩. get 불필요.
  • tuple-like: std::tuple_size<E>::value로 개수를 알고, 각 바인딩은 get<i>(e)(멤버 또는 비멤버) 결과의 별명. std::tuple, std::pair, std::array 및 사용자가 프로토콜을 구현한 타입.
  • 공개 데이터 멤버(구조체): 비정적 공개 멤버가 순서대로 각 바인딩. Point{x,y}가 이 경로.

흔한 오해·함정

  • "바인딩 이름은 참조다" — 아니다. decltype(rx)는 참조가 아니라 하위 객체의 타입(값 카테고리·cv 는 별개 규칙). 다만 동작은 별명이라 원본을 바꾼다(auto& 일 때).
  • auto 값 바인딩도 복사가 한 번 일어난다 — 큰 객체를 값으로 분해하면 전체 복사 비용. 읽기만 하면 const auto& 권장.
  • tuple-like 는 get<i>가 참조를 돌려주므로 auto&면 원본 tuple 원소를 별명. auto면 개별 원소가 아니라 tuple 전체 복사본에서 별명.
  • 구조화된 바인딩은 람다 캡처(C++17)나 constexpr 문맥에서 제약이 있었다(이후 완화). 캡처하려면 명시 캡처 주의.

면접 포인트

  • auto [a,b] 의 "숨은 객체 + 별명" 모델을 설명하고, auto vs auto& vs const auto&의 복사/참조/원본 반영 차이를 정확히 말할 수 있는가.
  • 세 가지 분해 경로(배열 / tuple-like get<i> / 공개 멤버)와 각각의 트리거 조건.
  • 큰 객체를 값으로 분해할 때의 복사 비용, 그리고 decltype(바인딩)이 참조가 아니라는 미묘함.