C++ tuple 상세 가이드 | "튜플" 가이드
이 글의 핵심
C++ tuple 상세 가이드에 대한 실전 가이드입니다.
들어가며
**std::tuple**은 C++11에서 도입된 타입으로, 여러 값을 하나로 묶어 저장합니다. 함수에서 여러 값을 반환하거나, 임시로 여러 값을 그룹화할 때 유용합니다.
#include <tuple>
#include <iostream>
#include <string>
int main() {
// tuple 생성: 여러 타입의 값을 하나로 묶음
// <int, double, std::string>: 저장할 타입들 (순서 중요)
std::tuple<int, double, std::string> t{42, 3.14, "hello"};
// 접근: std::get<인덱스>(tuple)
// 인덱스는 컴파일 타임 상수여야 함 (템플릿 인자)
int x = std::get<0>(t); // 첫 번째 요소 (int)
double y = std::get<1>(t); // 두 번째 요소 (double)
std::string z = std::get<2>(t); // 세 번째 요소 (string)
std::cout << x << ", " << y << ", " << z << std::endl;
return 0;
}
출력:
42, 3.14, hello
1. 기본 사용
생성
#include <tuple>
#include <iostream>
#include <string>
int main() {
// 방법 1: 직접 생성 (타입 명시)
std::tuple<int, double> t1{42, 3.14};
// 방법 2: make_tuple (타입 자동 추론)
// "hello"는 const char*로 추론됨
auto t2 = std::make_tuple(42, 3.14, "hello");
// 방법 3: C++17 CTAD (Class Template Argument Deduction)
// 타입 명시 없이 생성자 인자로부터 타입 추론
// std::string("world"): 명시적으로 string 타입 지정
std::tuple t3{42, 3.14, std::string("world")}; // C++17
// 크기: tuple의 요소 개수를 컴파일 타임에 확인
// std::tuple_size_v: tuple의 요소 개수 반환 (C++17)
// decltype(t1): t1의 타입 추출
constexpr size_t size1 = std::tuple_size_v<decltype(t1)>; // 2
constexpr size_t size2 = std::tuple_size_v<decltype(t2)>; // 3
std::cout << "t1 크기: " << size1 << std::endl;
std::cout << "t2 크기: " << size2 << std::endl;
return 0;
}
출력:
t1 크기: 2
t2 크기: 3
접근
#include <tuple>
#include <iostream>
#include <string>
int main() {
std::tuple<int, double, std::string> t{42, 3.14, "hello"};
// 인덱스로 접근
int x = std::get<0>(t);
double y = std::get<1>(t);
std::string z = std::get<2>(t);
std::cout << "x: " << x << std::endl;
std::cout << "y: " << y << std::endl;
std::cout << "z: " << z << std::endl;
// 타입으로 접근 (타입이 유일할 때)
int x2 = std::get<int>(t);
double y2 = std::get<double>(t);
std::string z2 = std::get<std::string>(t);
std::cout << "x2: " << x2 << std::endl;
return 0;
}
출력:
x: 42
y: 3.14
z: hello
x2: 42
2. structured binding (C++17)
#include <tuple>
#include <iostream>
#include <string>
std::tuple<int, std::string, bool> parseUser(const std::string& data) {
// 파싱 로직 (예시)
int id = 123;
std::string name = "Alice";
bool active = true;
// tuple 반환: 여러 값을 한 번에 반환
// {id, name, active}: 중괄호 초기화로 tuple 생성
return {id, name, active};
}
int main() {
// C++17 structured binding: tuple을 개별 변수로 분해
// auto: 타입 자동 추론 (tuple<int, string, bool>)
// [id, name, active]: 각 요소를 받을 변수 이름
// 순서대로 tuple의 요소가 할당됨
auto [id, name, active] = parseUser("data");
std::cout << "ID: " << id << std::endl;
std::cout << "이름: " << name << std::endl;
std::cout << "활성: " << (active ? "true" : "false") << std::endl;
return 0;
}
출력:
ID: 123
이름: Alice
활성: true
3. tie
기존 변수에 언팩
#include <tuple>
#include <iostream>
std::tuple<int, double> getValues() {
return {42, 3.14};
}
int main() {
// 기존 변수 선언 (초기화 안됨)
int x;
double y;
// std::tie: 기존 변수들을 참조로 묶어 tuple 생성
// getValues()의 반환값이 x, y에 각각 할당됨
// structured binding과 달리 기존 변수를 재사용
std::tie(x, y) = getValues();
std::cout << "x: " << x << std::endl;
std::cout << "y: " << y << std::endl;
// std::ignore: 특정 요소를 무시하고 싶을 때 사용
// 두 번째 요소(double)는 버리고 첫 번째만 받음
std::tie(x, std::ignore) = getValues();
std::cout << "x (업데이트): " << x << std::endl;
return 0;
}
출력:
x: 42
y: 3.14
x (업데이트): 42
비교 연산
#include <tuple>
#include <iostream>
#include <string>
struct Person {
std::string name;
int age;
double height;
// 튜플로 비교
auto asTuple() const {
return std::tie(name, age, height);
}
bool operator<(const Person& other) const {
return asTuple() < other.asTuple();
}
bool operator==(const Person& other) const {
return asTuple() == other.asTuple();
}
};
int main() {
Person p1{"Alice", 25, 165.5};
Person p2{"Bob", 30, 175.0};
Person p3{"Alice", 25, 165.5};
if (p1 < p2) {
std::cout << "p1 < p2" << std::endl;
}
if (p1 == p3) {
std::cout << "p1 == p3" << std::endl;
}
return 0;
}
출력:
p1 < p2
p1 == p3
4. 실전 예제
예제 1: 여러 값 반환
#include <tuple>
#include <iostream>
#include <string>
#include <cmath>
std::tuple<double, double, double> solveQuadratic(double a, double b, double c) {
double discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
return {NAN, NAN, discriminant};
}
double sqrtD = std::sqrt(discriminant);
double x1 = (-b + sqrtD) / (2 * a);
double x2 = (-b - sqrtD) / (2 * a);
return {x1, x2, discriminant};
}
int main() {
auto [x1, x2, discriminant] = solveQuadratic(1, -5, 6);
if (std::isnan(x1)) {
std::cout << "실근 없음 (판별식: " << discriminant << ")" << std::endl;
} else {
std::cout << "x1: " << x1 << std::endl;
std::cout << "x2: " << x2 << std::endl;
std::cout << "판별식: " << discriminant << std::endl;
}
return 0;
}
출력:
x1: 3
x2: 2
판별식: 1
예제 2: 컨테이너 정렬
#include <tuple>
#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
int main() {
std::vector<std::tuple<int, std::string, double>> students = {
{1, "Alice", 85.5},
{2, "Bob", 92.0},
{3, "Charlie", 78.5},
{4, "David", 92.0},
{5, "Eve", 85.5}
};
// 점수로 내림차순 정렬
std::sort(students.begin(), students.end(),
{
return std::get<2>(a) > std::get<2>(b);
});
std::cout << "=== 점수순 ===" << std::endl;
for (const auto& [id, name, score] : students) {
std::cout << name << ": " << score << "점" << std::endl;
}
// 점수, 이름으로 정렬
std::sort(students.begin(), students.end(),
{
auto score_a = std::get<2>(a);
auto score_b = std::get<2>(b);
if (score_a != score_b) {
return score_a > score_b;
}
return std::get<1>(a) < std::get<1>(b);
});
std::cout << "\n=== 점수, 이름순 ===" << std::endl;
for (const auto& [id, name, score] : students) {
std::cout << name << ": " << score << "점" << std::endl;
}
return 0;
}
출력:
=== 점수순 ===
Bob: 92점
David: 92점
Alice: 85.5점
Eve: 85.5점
Charlie: 78.5점
=== 점수, 이름순 ===
Bob: 92점
David: 92점
Alice: 85.5점
Eve: 85.5점
Charlie: 78.5점
예제 3: 맵 키
#include <tuple>
#include <map>
#include <iostream>
#include <string>
int main() {
// 복합 키
std::map<std::tuple<int, std::string>, double> scores;
scores[{1, "Math"}] = 85.5;
scores[{1, "English"}] = 92.0;
scores[{2, "Math"}] = 78.5;
scores[{2, "English"}] = 88.0;
// 조회
auto key = std::make_tuple(1, "Math");
if (auto it = scores.find(key); it != scores.end()) {
auto [student_subject, score] = *it;
auto [id, subject] = student_subject;
std::cout << "학생 " << id << ", " << subject << ": " << score << "점" << std::endl;
}
// 순회
std::cout << "\n=== 전체 점수 ===" << std::endl;
for (const auto& [key, score] : scores) {
auto [id, subject] = key;
std::cout << "학생 " << id << ", " << subject << ": " << score << "점" << std::endl;
}
return 0;
}
출력:
학생 1, Math: 85.5점
=== 전체 점수 ===
학생 1, English: 92점
학생 1, Math: 85.5점
학생 2, English: 88점
학생 2, Math: 78.5점
5. tuple 연산
비교
#include <tuple>
#include <iostream>
int main() {
std::tuple<int, double> t1{42, 3.14};
std::tuple<int, double> t2{42, 3.14};
std::tuple<int, double> t3{42, 2.71};
// 비교
std::cout << "t1 == t2: " << (t1 == t2) << std::endl; // 1
std::cout << "t1 == t3: " << (t1 == t3) << std::endl; // 0
std::cout << "t1 < t3: " << (t1 < t3) << std::endl; // 0
std::cout << "t3 < t1: " << (t3 < t1) << std::endl; // 1
return 0;
}
출력:
t1 == t2: 1
t1 == t3: 0
t1 < t3: 0
t3 < t1: 1
연결 (tuple_cat)
#include <tuple>
#include <iostream>
#include <string>
int main() {
std::tuple<int, double> t1{42, 3.14};
std::tuple<std::string, bool> t2{"hello", true};
// 연결
auto t3 = std::tuple_cat(t1, t2);
// std::tuple<int, double, std::string, bool>
auto [x, y, z, w] = t3;
std::cout << x << ", " << y << ", " << z << ", " << w << std::endl;
return 0;
}
출력:
42, 3.14, hello, 1
6. 자주 발생하는 문제
문제 1: 인덱스
#include <tuple>
#include <iostream>
int main() {
std::tuple<int, double, std::string> t{42, 3.14, "hello"};
// ❌ 런타임 인덱스
// int i = 0;
// auto x = std::get<i>(t); // 에러: i는 constexpr이 아님
// ✅ 컴파일 타임 인덱스
auto x = std::get<0>(t);
std::cout << "x: " << x << std::endl;
return 0;
}
문제 2: 참조
#include <tuple>
#include <iostream>
int main() {
int x = 42;
double y = 3.14;
// ❌ 복사
auto t1 = std::make_tuple(x, y);
std::get<0>(t1) = 100;
std::cout << "x: " << x << std::endl; // 42 (변경 안됨)
// ✅ 참조
auto t2 = std::tie(x, y);
std::get<0>(t2) = 100;
std::cout << "x: " << x << std::endl; // 100 (변경됨)
// ✅ forward_as_tuple
auto t3 = std::forward_as_tuple(x, y);
std::get<0>(t3) = 200;
std::cout << "x: " << x << std::endl; // 200
return 0;
}
출력:
x: 42
x: 100
x: 200
문제 3: 크기
#include <tuple>
#include <iostream>
struct Point {
int x, y;
};
int main() {
// tuple은 오버헤드 있음
std::tuple<int, int> t;
std::cout << "tuple 크기: " << sizeof(t) << std::endl; // 8
// struct는 오버헤드 없음
Point p;
std::cout << "struct 크기: " << sizeof(p) << std::endl; // 8
// 복잡한 tuple
std::tuple<int, double, std::string> t2;
std::cout << "복잡한 tuple 크기: " << sizeof(t2) << std::endl; // 48
return 0;
}
출력:
tuple 크기: 8
struct 크기: 8
복잡한 tuple 크기: 48
7. pair vs tuple
비교
#include <tuple>
#include <utility>
#include <iostream>
int main() {
// pair: 2개
std::pair<int, double> p{42, 3.14};
std::cout << "pair: " << p.first << ", " << p.second << std::endl;
// tuple: 여러 개
std::tuple<int, double, std::string> t{42, 3.14, "hello"};
std::cout << "tuple: " << std::get<0>(t) << ", "
<< std::get<1>(t) << ", "
<< std::get<2>(t) << std::endl;
return 0;
}
출력:
pair: 42, 3.14
tuple: 42, 3.14, hello
선택 가이드
| 특징 | pair | tuple |
|---|---|---|
| 요소 개수 | 2개 | 여러 개 |
| 접근 | .first, .second | std::get<N> |
| 가독성 | 높음 | 낮음 |
| 용도 | 키-값, 좌표 | 여러 값 반환 |
// ✅ pair: 2개일 때
std::pair<int, std::string> getUserInfo() {
return {123, "Alice"};
}
// ✅ tuple: 3개 이상
std::tuple<int, std::string, int, double> getUserDetails() {
return {123, "Alice", 25, 165.5};
}
// ✅ struct: 이름 있는 필드 (권장)
struct User {
int id;
std::string name;
int age;
double height;
};
8. 실전 예제: 데이터베이스 결과
#include <tuple>
#include <vector>
#include <iostream>
#include <string>
using Row = std::tuple<int, std::string, int, double>;
std::vector<Row> queryDatabase() {
return {
{1, "Alice", 25, 85.5},
{2, "Bob", 30, 92.0},
{3, "Charlie", 28, 78.5}
};
}
void printResults(const std::vector<Row>& results) {
std::cout << "ID\tName\tAge\tScore" << std::endl;
std::cout << "---\t----\t---\t-----" << std::endl;
for (const auto& [id, name, age, score] : results) {
std::cout << id << "\t" << name << "\t" << age << "\t" << score << std::endl;
}
}
double calculateAverage(const std::vector<Row>& results) {
double total = 0;
for (const auto& [id, name, age, score] : results) {
total += score;
}
return total / results.size();
}
int main() {
auto results = queryDatabase();
printResults(results);
std::cout << "\n평균 점수: " << calculateAverage(results) << std::endl;
return 0;
}
출력:
ID Name Age Score
--- ---- --- -----
1 Alice 25 85.5
2 Bob 30 92
3 Charlie 28 78.5
평균 점수: 85.3333
정리
핵심 요약
- tuple: 여러 값 묶기 (C++11)
- 접근:
std::get<N>, structured binding - tie: 기존 변수에 언팩
- 비교: 사전식 비교
- 실무: 여러 값 반환, 임시 그룹화
pair vs tuple
| 특징 | pair | tuple |
|---|---|---|
| 요소 개수 | 2개 | 여러 개 |
| 접근 | .first, .second | std::get<N> |
| 가독성 | 높음 | 낮음 |
| 크기 | 작음 | 큼 |
| 용도 | 키-값, 좌표 | 여러 값 반환 |
실전 팁
사용 원칙:
- 여러 값 반환
- 임시 그룹화
- 비교 연산
- 맵 복합 키
성능:
- 스택 할당
- 오버헤드 있음 (패딩)
- 작은 경우 struct 권장
- 컴파일 타임 크기
주의사항:
- 인덱스는 constexpr
- 참조는 tie/forward_as_tuple
- 가독성 (struct 고려)
- 크기 오버헤드
다음 단계
- C++ Structured Binding
- C++ pair
- C++ optional
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ tuple | “튜플” 완벽 가이드
- C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문
- C++ Memory Order | “메모리 순서” 가이드
관련 글
- C++ tuple 핵심 요약 |
- C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문
- C++ async & launch |
- C++ Atomic Operations |
- C++ Attributes |