콘텐츠로 이동

C++17 Structured Bindings 완벽 가이드

C++17의 Structured Bindings(구조적 바인딩)는 배열, 구조체, 튜플, pair 등 복합 타입의 요소들을 개별 변수에 한 번에 바인딩할 수 있게 해주는 문법입니다. Python의 언패킹과 유사하지만 C++의 타입 시스템 안에서 동작합니다.


auto [변수1, 변수2, ...] = 표현식;

auto 뒤에 대괄호로 변수명 목록을 지정하면, 우변 표현식의 각 요소가 해당 변수에 바인딩됩니다.


#include <iostream>
int arr[3] = {10, 20, 30};
auto [a, b, c] = arr; // a=10, b=20, c=30
std::cout << a << ", " << b << ", " << c << "\n"; // 10, 20, 30
#include <map>
#include <string>
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "\n";
}
// Alice: 95
// Bob: 87
#include <tuple>
auto getData() {
return std::make_tuple(42, 3.14, std::string("hello"));
}
auto [id, ratio, label] = getData();
std::cout << id << ", " << ratio << ", " << label << "\n"; // 42, 3.14, hello

멤버 변수가 public인 경우 자동으로 분해됩니다.

struct Point {
double x;
double y;
double z;
};
Point origin{1.0, 2.0, 3.0};
auto [px, py, pz] = origin;
std::cout << px << ", " << py << ", " << pz << "\n"; // 1, 2, 3

auto&로 선언하면 원본 데이터에 대한 참조로 바인딩됩니다.

std::pair<int, std::string> item{1, "apple"};
auto& [id, name] = item;
name = "banana"; // item.second도 변경됨
std::cout << item.second << "\n"; // banana

const auto&는 읽기 전용 참조입니다.

for (const auto& [key, val] : myMap) {
// key, val 수정 불가
std::cout << key << ": " << val << "\n";
}

컴파일러는 structured binding을 다음과 같이 변환합니다.

// 원본 코드
auto [x, y] = std::make_pair(1, 2);
// 컴파일러가 생성하는 코드 (개념적 표현)
auto __e = std::make_pair(1, 2);
using __E = decltype(__e);
auto& x = std::get<0>(__e);
auto& y = std::get<1>(__e);

실제로는 각 바인딩 변수가 내부 임시 객체의 멤버에 대한 참조로 처리됩니다. 따라서 임시 객체의 생명주기가 바인딩 변수의 생명주기와 일치합니다.


5. 사용자 정의 타입에 get<> 지원 추가

섹션 제목: “5. 사용자 정의 타입에 get<> 지원 추가”

std::get<>std::tuple_size, std::tuple_element를 특수화하면 임의의 타입도 structured binding을 지원할 수 있습니다.

#include <tuple>
class Color {
public:
uint8_t r, g, b;
Color(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {}
};
// std::tuple_size 특수화
template<>
struct std::tuple_size<Color> : std::integral_constant<std::size_t, 3> {};
// std::tuple_element 특수화
template<std::size_t I>
struct std::tuple_element<I, Color> {
using type = uint8_t;
};
// get<> 함수 구현
template<std::size_t I>
uint8_t get(const Color& c) {
if constexpr (I == 0) return c.r;
else if constexpr (I == 1) return c.g;
else return c.b;
}
// 이제 structured binding 사용 가능
Color red{255, 0, 0};
auto [r, g, b] = red;
std::cout << (int)r << ", " << (int)g << ", " << (int)b << "\n"; // 255, 0, 0

struct ParseResult {
bool success;
int value;
std::string error;
};
ParseResult parseInteger(const std::string& s) {
try {
return {true, std::stoi(s), ""};
} catch (...) {
return {false, 0, "Invalid integer: " + s};
}
}
auto [ok, val, err] = parseInteger("42");
if (ok) {
std::cout << "Parsed: " << val << "\n";
} else {
std::cout << "Error: " << err << "\n";
}
std::map<std::string, int> registry;
auto [it, inserted] = registry.emplace("item", 42);
if (inserted) {
std::cout << "새 항목 삽입: " << it->second << "\n";
} else {
std::cout << "이미 존재: " << it->second << "\n";
}
#include <ranges>
std::vector<std::string> items = {"alpha", "beta", "gamma"};
for (auto [i, val] : std::views::enumerate(items)) { // C++23
std::cout << i << ": " << val << "\n";
}

  • 바인딩 변수 수는 분해되는 요소 수와 정확히 일치해야 합니다.
  • auto&&는 universal reference로 동작하여 이동 의미론을 활용할 수 있습니다.
  • 중첩 structured binding은 지원되지 않습니다(auto [[a,b], c] 불가).
  • 비트필드에는 structured binding이 적용되지 않습니다.

Structured Bindings는 코드의 가독성을 크게 향상시키는 C++17의 핵심 기능입니다. 특히 범위 기반 for 루프에서 map을 순회하거나, 함수에서 여러 값을 반환받을 때 강력한 표현력을 발휘합니다. auto, auto&, const auto&, auto&&의 차이를 명확히 이해하고 상황에 맞게 선택하는 것이 중요합니다.