C++ tuple 상세 가이드 | "튜플" 가이드

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

선택 가이드

특징pairtuple
요소 개수2개여러 개
접근.first, .secondstd::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

정리

핵심 요약

  1. tuple: 여러 값 묶기 (C++11)
  2. 접근: std::get<N>, structured binding
  3. tie: 기존 변수에 언팩
  4. 비교: 사전식 비교
  5. 실무: 여러 값 반환, 임시 그룹화

pair vs tuple

특징pairtuple
요소 개수2개여러 개
접근.first, .secondstd::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 |