C++ Structured Binding | '구조적 바인딩' C++17 가이드
이 글의 핵심
C++ Structured Binding: "구조적 바인딩" C++17 가이드. 기본 사용·참조와 const.
들어가며
Structured Binding (구조적 바인딩)은 C++17에서 도입된 기능으로, 튜플, 배열, 구조체 등을 여러 변수로 한 번에 분해할 수 있습니다.
#include <tuple>
#include <iostream>
#include <string>
int main() {
// tuple 생성: 여러 타입의 값을 하나로 묶음
std::tuple<int, double, std::string> t = {42, 3.14, "Hello"};
// 구조적 바인딩 (Structured Binding, C++17)
// auto [i, d, s]: tuple의 각 요소를 개별 변수로 분해
// i, d, s는 각각 tuple의 첫 번째, 두 번째, 세 번째 요소
// 타입은 자동 추론: i는 int, d는 double, s는 string
auto [i, d, s] = t;
std::cout << i << std::endl; // 42
std::cout << d << std::endl; // 3.14
std::cout << s << std::endl; // Hello
}
왜 필요한가?:
- 간결성: 여러 변수를 한 줄로 선언
- 가독성: 의미 있는 이름 부여
- 안전성: 타입 추론으로 실수 방지
- 편의성: 맵 순회, 함수 반환값 처리
// ❌ 기존 방식: 복잡
std::map<std::string, int> m;
for (const auto& pair : m) {
std::cout << pair.first << ": " << pair.second << '\n';
}
// ✅ 구조적 바인딩: 간결
for (const auto& [key, value] : m) {
std::cout << key << ": " << value << '\n';
}
1. 기본 사용
구조적 바인딩은 배열, 구조체, 튜플이라는 세 가지 대표적인 자료 형태를 모두 같은 문법(auto [a, b, c] = 대상;)으로 분해할 수 있습니다. 어떤 자료형이든 요소 개수와 변수 개수만 맞추면 컴파일러가 나머지를 알아서 처리해주기 때문에, 세 가지 형태를 하나씩 살펴보며 공통된 문법 감각을 익혀보겠습니다.
배열
고정 크기 배열은 구조적 바인딩이 지원하는 가장 단순한 형태입니다. 아래 int arr[] = {1, 2, 3};을 auto [a, b, c] = arr;로 분해하면 배열의 각 요소가 순서대로 a, b, c에 복사됩니다. 배열은 크기 정보가 타입에 포함되어 있어(int[3]), 컴파일러가 요소 개수를 정확히 알고 있으므로 변수 개수가 배열 크기와 다르면 즉시 컴파일 에러로 알려줍니다.
#include <iostream>
int main() {
int arr[] = {1, 2, 3};
auto [a, b, c] = arr;
std::cout << a << std::endl; // 1
std::cout << b << std::endl; // 2
std::cout << c << std::endl; // 3
return 0;
}
구조체
멤버 변수만 가진 단순한 집합체(aggregate) 구조체도 구조적 바인딩으로 분해할 수 있는데, 이때는 별도의 접근 함수 없이 멤버가 선언된 순서대로 변수에 대응됩니다. 아래 Point는 x, y 두 멤버를 가지고 있어 auto [x, y] = p;로 분해하면 x에는 p.x가, y에는 p.y가 매핑되는데, 멤버 이름과 바인딩 변수 이름이 우연히 같아 보일 뿐 실제로는 선언 순서만으로 대응 관계가 결정된다는 점을 알아두어야 합니다.
#include <iostream>
struct Point {
int x;
int y;
};
int main() {
Point p = {10, 20};
auto [x, y] = p;
std::cout << "x: " << x << std::endl; // 10
std::cout << "y: " << y << std::endl; // 20
return 0;
}
튜플
구조체와 달리 std::tuple은 이름 있는 멤버가 없기 때문에, 구조적 바인딩 이전에는 std::get<0>, std::get<1>처럼 인덱스로만 요소에 접근할 수 있었습니다. 아래 getData()가 반환하는 tuple<int, double, string>을 auto [i, d, s] = getData();로 즉시 분해하면, 함수가 여러 값을 반환하는 상황에서도 각 값에 인덱스 대신 의미 있는 이름을 바로 붙여 사용할 수 있어 코드의 의도가 훨씬 분명해집니다.
#include <tuple>
#include <iostream>
#include <string>
std::tuple<int, double, std::string> getData() {
return {42, 3.14, "Hello"};
}
int main() {
auto [i, d, s] = getData();
std::cout << "int: " << i << std::endl;
std::cout << "double: " << d << std::endl;
std::cout << "string: " << s << std::endl;
return 0;
}
2. 참조와 const
구조적 바인딩 앞에 붙이는 한정자(auto, auto&, const auto&)에 따라 바인딩된 변수가 원본의 복사본인지 참조인지가 완전히 달라집니다. 이 차이를 정확히 이해하지 못하면 원본을 수정했다고 착각하거나, 반대로 불필요한 복사로 성능을 낭비하는 실수를 하기 쉽습니다.
복사 vs 참조
auto [x1, y1] = p;처럼 그냥 auto를 쓰면 x1, y1은 p.x, p.y의 완전한 복사본이 되어, 이후 x1을 바꿔도 원본 p는 전혀 영향을 받지 않습니다. auto& [x2, y2] = p;처럼 &를 붙이면 x2, y2는 p.x, p.y의 별칭(참조)이 되어 그 값을 바꾸면 p도 즉시 함께 바뀌고, const auto& [x3, y3] = p;는 참조이되 수정은 금지해 불필요한 복사 없이 읽기 전용으로만 접근하고 싶을 때 가장 안전하고 효율적인 선택이 됩니다.
#include <iostream>
struct Point {
int x, y;
};
int main() {
Point p = {10, 20};
// 방법 1: 복사 (auto)
// x1, y1은 p.x, p.y의 복사본
// x1, y1 변경해도 p는 영향 없음
auto [x1, y1] = p;
x1 = 100; // x1만 변경 (p.x는 여전히 10)
std::cout << "p.x: " << p.x << std::endl; // 10
// 방법 2: 참조 (auto&)
// x2, y2는 p.x, p.y의 참조 (별칭)
// x2, y2 변경하면 p도 변경됨
auto& [x2, y2] = p;
x2 = 100; // p.x가 100으로 변경됨
std::cout << "p.x: " << p.x << std::endl; // 100
// 방법 3: const 참조 (const auto&)
// x3, y3는 p.x, p.y의 const 참조
// 읽기만 가능, 수정 불가 (불필요한 복사 방지)
const auto& [x3, y3] = p;
// x3 = 200; // ❌ 컴파일 에러: const 참조는 수정 불가
std::cout << "x3: " << x3 << std::endl; // 100
return 0;
}
출력:
p.x: 10
p.x: 100
x3: 100
3. 실전 예제
지금까지 배운 문법을 실무에서 가장 자주 마주치는 네 가지 상황에 적용해 보겠습니다. 맵 순회, 함수의 다중 반환값 처리, 표준 알고리즘이 반환하는 pair 언팩, 구조체 벡터 순회입니다.
예제 1: 맵 순회
std::map을 범위 기반 for로 순회하면 각 요소는 std::pair<const Key, Value> 타입인데, 구조적 바인딩이 없던 시절에는 pair.first, pair.second로 접근해야 해서 코드만 봐서는 무엇이 키이고 무엇이 값인지 즉시 알기 어려웠습니다. for (const auto& [name, score] : scores)처럼 구조적 바인딩을 쓰면 name과 score라는 의미 있는 이름이 코드에 바로 드러나고, const auto&를 사용해 매 순회마다 불필요한 복사도 피할 수 있습니다.
#include <map>
#include <iostream>
#include <string>
int main() {
std::map<std::string, int> scores = {
{"Alice", 90},
{"Bob", 85},
{"Charlie", 95}
};
std::cout << "=== 구조적 바인딩 ===" << std::endl;
// const auto& [name, score]: map의 각 요소(pair)를 분해
// name: pair.first (키)
// score: pair.second (값)
// const auto&: 복사 없이 const 참조로 접근 (효율적)
for (const auto& [name, score] : scores) {
// pair.first, pair.second 대신 의미 있는 이름 사용
std::cout << name << ": " << score << std::endl;
}
std::cout << "\n=== 기존 방식 ===" << std::endl;
// 기존 방식: pair 객체로 접근
// pair.first, pair.second는 의미가 불명확
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
두 방식 모두 같은 맵을 순회하므로 결과는 동일하지만, 코드의 가독성에서 차이가 드러납니다.
=== 구조적 바인딩 ===
Alice: 90
Bob: 85
Charlie: 95
=== 기존 방식 ===
Alice: 90
Bob: 85
Charlie: 95
예제 2: 함수 반환값
함수가 튜플이나 pair로 여러 값을 반환하는 것은 흔한 패턴이지만, 구조적 바인딩이 없다면 호출부에서 매번 std::get<0>, std::get<1>을 반복해야 했습니다. 아래 getRGB()와 divmod()는 각각 색상의 R/G/B 값과 몫/나머지를 튜플·pair로 반환하는데, auto [r, g, b] = getRGB();와 auto [quotient, remainder] = divmod(17, 5);처럼 호출과 동시에 의미 있는 이름으로 분해하면 함수 시그니처만 보고도 반환값의 의미를 짐작하기 쉬워집니다.
#include <tuple>
#include <iostream>
std::tuple<int, int, int> getRGB() {
return {255, 128, 64};
}
std::pair<int, int> divmod(int dividend, int divisor) {
return {dividend / divisor, dividend % divisor};
}
int main() {
auto [r, g, b] = getRGB();
std::cout << "R: " << r << std::endl;
std::cout << "G: " << g << std::endl;
std::cout << "B: " << b << std::endl;
auto [quotient, remainder] = divmod(17, 5);
std::cout << "17 / 5 = " << quotient << " ... " << remainder << std::endl;
return 0;
}
출력:
R: 255
G: 128
B: 64
17 / 5 = 3 ... 2
예제 3: pair 언팩
표준 라이브러리의 여러 함수가 결과를 pair로 반환하도록 설계되어 있어, 구조적 바인딩과 특히 궁합이 좋습니다. std::minmax_element는 최솟값과 최댓값의 위치를 가리키는 반복자 쌍을 반환하고, std::map::insert는 삽입된(또는 이미 있던) 위치의 반복자와 실제로 삽입이 일어났는지를 나타내는 bool을 pair로 반환합니다. 아래 예제처럼 auto [minIt, maxIt] = ...와 auto [it, inserted] = ...로 즉시 분해하면, 반환된 pair의 각 요소가 무엇을 의미하는지 변수 이름만으로 명확해집니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
auto [minIt, maxIt] = std::minmax_element(v.begin(), v.end());
std::cout << "최소: " << *minIt << std::endl; // 1
std::cout << "최대: " << *maxIt << std::endl; // 9
// 삽입 결과
std::map<std::string, int> m;
auto [it, inserted] = m.insert({"key", 10});
if (inserted) {
std::cout << "삽입 성공: " << it->first << " = " << it->second << std::endl;
}
return 0;
}
출력:
최소: 1
최대: 9
삽입 성공: key = 10
예제 4: 구조체 분해
구조적 바인딩은 단일 구조체뿐 아니라 구조체로 이루어진 컨테이너를 순회할 때도 그대로 적용됩니다. 아래 std::vector<Person>을 for (const auto& [name, age, height] : people)로 순회하면, 매 반복마다 Person 객체 하나가 세 개의 이름 있는 변수로 즉시 분해되어 person.name, person.age, person.height처럼 멤버 접근 구문을 반복해서 쓸 필요가 없어집니다. 필드가 많은 구조체를 순회할수록 이 방식이 코드를 얼마나 간결하게 만들어주는지 체감하게 됩니다.
#include <vector>
#include <iostream>
#include <string>
struct Person {
std::string name;
int age;
double height;
};
int main() {
std::vector<Person> people = {
{"Alice", 25, 165.5},
{"Bob", 30, 175.0},
{"Charlie", 28, 180.5}
};
for (const auto& [name, age, height] : people) {
std::cout << name << " (" << age << "세, " << height << "cm)" << std::endl;
}
return 0;
}
출력:
Alice (25세, 165.5cm)
Bob (30세, 175cm)
Charlie (28세, 180.5cm)
4. 자주 발생하는 문제
구조적 바인딩은 문법이 간단한 만큼, 규칙을 정확히 모르면 오히려 당황스러운 컴파일 에러를 마주치기 쉽습니다. 요소 개수, 참조 대상의 수명, 타입 명시라는 세 가지 흔한 실수를 짚어보겠습니다.
문제 1: 요소 개수 불일치
구조적 바인딩의 변수 개수는 분해 대상의 요소 개수와 정확히 일치해야 합니다. 아래 std::tuple<int, int, int>는 요소가 3개인데 auto [a, b] = t;처럼 변수를 2개만 선언하면, 컴파일러는 이를 부분적으로 분해하는 것을 허용하지 않고 즉시 컴파일 에러를 발생시킵니다. 배열 일부만 취급하고 싶은 상황이 흔하다면, 구조적 바인딩 대신 인덱스로 필요한 요소만 골라 쓰는 것이 더 적합합니다.
#include <tuple>
int main() {
std::tuple<int, int, int> t = {1, 2, 3};
// ❌ 에러: 3개인데 2개만
// auto [a, b] = t;
// ✅ 개수 일치
auto [a, b, c] = t;
return 0;
}
문제 2: 참조 수명
auto&로 참조 바인딩을 만들 때는 참조 대상이 바인딩보다 오래 살아있어야 한다는 일반적인 레퍼런스 수명 규칙이 그대로 적용됩니다. auto& [x, y] = Point{10, 20};처럼 임시 객체를 직접 참조로 분해하려 하면, 그 임시 객체는 문장이 끝나는 즉시 소멸되므로 x, y는 댕글링 참조가 됩니다. 임시값을 다뤄야 한다면 auto로 값을 복사해 받거나(auto [x1, y1] = Point{10, 20};), 아래 두 번째 예제처럼 먼저 이름 있는 변수에 저장한 뒤 그 변수를 참조로 분해해야 안전합니다.
#include <iostream>
struct Point {
int x, y;
};
int main() {
// ❌ 댕글링 참조
// auto& [x, y] = Point{10, 20}; // 임시 객체
// x, y는 댕글링 참조!
// ✅ 복사
auto [x1, y1] = Point{10, 20};
std::cout << "x1: " << x1 << ", y1: " << y1 << std::endl;
// ✅ 변수 저장
Point p = {10, 20};
auto& [x2, y2] = p;
x2 = 100;
std::cout << "p.x: " << p.x << std::endl; // 100
return 0;
}
출력:
x1: 10, y1: 20
p.x: 100
문제 3: 타입 추론
구조적 바인딩의 각 변수는 반드시 auto를 통해서만 타입을 얻을 수 있고, auto [int x, double y] = ...;처럼 개별 변수마다 타입을 직접 명시하는 문법은 허용되지 않습니다. 이는 구조적 바인딩이 애초에 “대상의 실제 타입에 맞춰 각 변수의 타입을 자동으로 결정한다”는 목적으로 설계되었기 때문으로, 타입을 강제로 지정하고 싶다면 구조적 바인딩 이전 단계에서 값을 원하는 타입으로 먼저 변환해두어야 합니다.
#include <tuple>
int main() {
// ❌ 타입 명시 불가
// auto [int x, double y] = std::tuple{1, 2.0}; // 에러
// ✅ auto만 가능
auto [x, y] = std::tuple{1, 2.0};
return 0;
}
5. 실무 패턴
지금까지의 개념을 실제 프로젝트에서 자주 쓰이는 세 가지 패턴으로 정리하면, 에러 처리, 다중 반환값 활용, 맵 데이터의 읽기·수정 순회로 요약할 수 있습니다.
패턴 1: 에러 처리
예외를 던지지 않고 성공 여부와 결과 메시지를 함께 반환하고 싶을 때, std::pair<bool, std::string>과 구조적 바인딩을 조합하면 깔끔한 에러 처리 코드를 작성할 수 있습니다. 아래 parseConfig는 성공 여부를 bool로, 성공 메시지나 에러 이유를 string으로 함께 반환하고, 호출부에서는 auto [success, message] = parseConfig(...);로 즉시 분해해 if (!success)로 실패를 조기에 처리합니다. 이런 패턴은 예외 처리 비용을 피하면서도 실패 원인을 함께 전달해야 하는 파일 파싱, 설정 로딩 같은 상황에 특히 적합합니다.
#include <iostream>
#include <string>
#include <fstream>
std::pair<bool, std::string> parseConfig(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
return {false, "파일 없음"};
}
// 파싱 로직
std::string content;
if (!std::getline(file, content)) {
return {false, "파싱 실패"};
}
return {true, "성공"};
}
int main() {
auto [success, message] = parseConfig("config.json");
if (!success) {
std::cerr << "에러: " << message << std::endl;
return 1;
}
std::cout << "결과: " << message << std::endl;
return 0;
}
패턴 2: 다중 반환값
몫과 나머지처럼 서로 밀접하게 관련된 여러 결과값을 함께 반환해야 할 때, 튜플과 구조적 바인딩의 조합이 진가를 발휘합니다. 아래 divmod는 몫, 나머지, 부호라는 세 가지 값을 한 번의 계산으로 구해 튜플로 반환하고, 호출부는 auto [q, r, s] = divmod(17, 5);로 세 값을 한 줄에 받아옵니다. 음수 나눗셈처럼 부호 처리가 까다로운 연산일수록, 관련된 값들을 하나의 반환값으로 묶어두면 값들 사이의 일관성을 유지하기가 더 쉬워집니다.
#include <tuple>
#include <iostream>
std::tuple<int, int, int> divmod(int dividend, int divisor) {
int quotient = dividend / divisor;
int remainder = dividend % divisor;
int sign = (dividend < 0) ^ (divisor < 0) ? -1 : 1;
return {quotient, remainder, sign};
}
int main() {
auto [q, r, s] = divmod(17, 5);
std::cout << "몫: " << q << ", 나머지: " << r << ", 부호: " << s << std::endl;
// 몫: 3, 나머지: 2, 부호: 1
auto [q2, r2, s2] = divmod(-17, 5);
std::cout << "몫: " << q2 << ", 나머지: " << r2 << ", 부호: " << s2 << std::endl;
// 몫: -3, 나머지: -2, 부호: -1
return 0;
}
출력:
몫: 3, 나머지: 2, 부호: 1
몫: -3, 나머지: -2, 부호: -1
패턴 3: 범위 기반 for
2절에서 배운 const auto&와 auto&의 차이를 값 종류가 복잡한 맵에 적용해보는 예제입니다. 아래 첫 번째 순회는 const auto& [key, values]로 읽기 전용 접근만 하고, 두 번째 순회는 auto& [key, values]로 참조를 얻어 각 벡터에 push_back(0)을 호출해 맵에 담긴 데이터를 직접 수정합니다. 읽기만 할 때는 const auto&로 불필요한 복사를 막고, 수정이 필요할 때만 auto&로 바꾸는 습관을 들이면 의도치 않은 원본 변경을 방지하면서도 성능을 챙길 수 있습니다.
#include <map>
#include <vector>
#include <iostream>
#include <string>
int main() {
std::map<std::string, std::vector<int>> data = {
{"A", {1, 2, 3}},
{"B", {4, 5, 6}},
{"C", {7, 8, 9}}
};
// 맵 순회
std::cout << "=== 읽기 ===" << std::endl;
for (const auto& [key, values] : data) {
std::cout << key << ": ";
for (int v : values) {
std::cout << v << ' ';
}
std::cout << std::endl;
}
// 수정
std::cout << "\n=== 수정 ===" << std::endl;
for (auto& [key, values] : data) {
values.push_back(0); // 각 벡터에 0 추가
}
for (const auto& [key, values] : data) {
std::cout << key << ": ";
for (int v : values) {
std::cout << v << ' ';
}
std::cout << std::endl;
}
return 0;
}
읽기 순회와 수정 순회를 차례로 실행하면 각 벡터에 0이 추가된 결과가 다음처럼 출력됩니다.
=== 읽기 ===
A: 1 2 3
B: 4 5 6
C: 7 8 9
=== 수정 ===
A: 1 2 3 0
B: 4 5 6 0
C: 7 8 9 0
6. 커스텀 타입 지원
지금까지 본 배열, 구조체, 튜플은 모두 표준이나 컴파일러가 기본적으로 분해 방법을 알고 있는 타입이었지만, 직접 정의한 클래스도 “튜플 프로토콜”이라 불리는 세 가지 요소(get<I>() 멤버 함수, std::tuple_size 특수화, std::tuple_element 특수화)를 구현하면 구조적 바인딩의 대상이 될 수 있습니다. 아래 MyClass는 get<0>()과 get<1>()로 각각 x, y를 반환하도록 구현하고, std::tuple_size<MyClass>와 std::tuple_element<I, MyClass>를 특수화해 컴파일러에게 “이 클래스는 2개의 int 요소로 분해할 수 있다”는 정보를 알려줍니다. 이렇게 튜플 프로토콜을 구현해두면 auto [a, b] = obj;처럼 마치 내장 타입인 것처럼 자연스럽게 구조적 바인딩을 사용할 수 있어, 커스텀 좌표 클래스나 값 래퍼 타입을 라이브러리 사용자에게 더 편리한 인터페이스로 제공할 수 있습니다.
#include <iostream>
class MyClass {
public:
int x, y;
MyClass(int x, int y) : x(x), y(y) {}
// 튜플 프로토콜 구현
template<size_t I>
auto& get() {
if constexpr (I == 0) return x;
else if constexpr (I == 1) return y;
}
template<size_t I>
const auto& get() const {
if constexpr (I == 0) return x;
else if constexpr (I == 1) return y;
}
};
// 특수화
namespace std {
template<>
struct tuple_size<MyClass> : integral_constant<size_t, 2> {};
template<size_t I>
struct tuple_element<I, MyClass> {
using type = int;
};
}
int main() {
MyClass obj{10, 20};
auto [a, b] = obj;
std::cout << a << ", " << b << std::endl; // 10, 20
return 0;
}
7. 실전 예제: 데이터 처리
마지막으로 지금까지 배운 구조체 분해, 맵 순회, 함수 반환값 처리를 하나로 엮은 종합 예제입니다. groupByGrade는 학생 벡터를 순회하며 const auto& [name, score, grade]로 각 학생 정보를 분해한 뒤 성적 등급별 맵으로 재구성하고, printStatistics는 다시 그 맵을 const auto& [grade, students]로 순회하며 각 등급의 평균 점수를 계산합니다. 여러 겹으로 중첩된 구조적 바인딩(맵 순회 안에서 다시 벡터 순회, 그 안에서 다시 구조체 분해)이 코드 곳곳에서 반복되지만, 각 단계마다 의미 있는 이름이 붙어 있어 데이터가 어떻게 흘러가는지 전체 흐름을 따라가기가 어렵지 않습니다.
#include <vector>
#include <map>
#include <iostream>
#include <string>
#include <algorithm>
struct Student {
std::string name;
int score;
std::string grade;
};
std::map<std::string, std::vector<Student>> groupByGrade(const std::vector<Student>& students) {
std::map<std::string, std::vector<Student>> groups;
for (const auto& [name, score, grade] : students) {
groups[grade].push_back({name, score, grade});
}
return groups;
}
void printStatistics(const std::map<std::string, std::vector<Student>>& groups) {
for (const auto& [grade, students] : groups) {
int total = 0;
for (const auto& [name, score, g] : students) {
total += score;
}
double average = static_cast<double>(total) / students.size();
std::cout << "등급 " << grade << ": "
<< students.size() << "명, 평균 "
<< average << "점" << std::endl;
}
}
int main() {
std::vector<Student> students = {
{"Alice", 95, "A"},
{"Bob", 85, "B"},
{"Charlie", 92, "A"},
{"David", 78, "C"},
{"Eve", 88, "B"}
};
auto groups = groupByGrade(students);
std::cout << "=== 학생 목록 ===" << std::endl;
for (const auto& [grade, studentList] : groups) {
std::cout << "등급 " << grade << ":" << std::endl;
for (const auto& [name, score, g] : studentList) {
std::cout << " " << name << ": " << score << "점" << std::endl;
}
}
std::cout << "\n=== 통계 ===" << std::endl;
printStatistics(groups);
return 0;
}
위 main 함수를 실행하면 등급별 학생 목록과 통계가 다음처럼 출력됩니다.
=== 학생 목록 ===
등급 A:
Alice: 95점
Charlie: 92점
등급 B:
Bob: 85점
Eve: 88점
등급 C:
David: 78점
=== 통계 ===
등급 A: 2명, 평균 93.5점
등급 B: 2명, 평균 86.5점
등급 C: 1명, 평균 78점
정리
핵심 요약
- 구조적 바인딩: 튜플/배열/구조체 분해
- auto: 복사,
auto&: 참조,const auto&: const 참조 - 맵 순회:
for (const auto& [key, value] : map) - 함수 반환:
auto [a, b] = func() - 성능: 컴파일 타임, 오버헤드 없음
지원 타입
| 타입 | 예시 | 설명 |
|---|---|---|
| 배열 | int arr[3] | 고정 크기 배열 |
| 튜플 | std::tuple<int, double> | std::tuple, std::pair |
| 구조체 | struct Point { int x, y; } | 집합체 (aggregate) |
| 커스텀 | get<I>() 구현 | 튜플 프로토콜 |
실전 팁
사용 원칙:
- 맵 순회:
const auto& [key, value] - 함수 반환:
auto [a, b] = func() - 읽기만:
const auto& - 수정:
auto&
성능:
- 컴파일 타임 처리
- 런타임 오버헤드 없음
- 복사 방지 (참조 사용)
- 가독성 향상
주의사항:
- 요소 개수 일치
- 참조 수명 관리
- 타입 명시 불가
- 중첩 분해 불가 (C++17)
다음 단계
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Structured Binding 고급 | “구조화 바인딩” 가이드
- C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문
- C++ tuple apply | “튜플 적용” 가이드
관련 글
- C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문
- C++ Structured Binding 고급
- C++ any
- C++ auto 키워드
- C++ auto 타입 추론 | 복잡한 타입을 컴파일러에 맡기기
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Everything about C++ Structured Binding : from basic concepts to practical applications.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
이 글에서 다루는 키워드 (관련 검색어)
C++, structured-binding, C++17, auto, 언팩 등으로 검색하시면 이 글이 도움이 됩니다.