C++ tuple detailed guide | "Tuple" guide

C++ tuple detailed guide | "Tuple" guide

이 글의 핵심

This is a practical guide to the detailed C++ tuple guide.

Entering

std::tuple is a type introduced in C++11 that stores multiple values ​​together. This is useful when returning multiple values ​​from a function or temporarily grouping multiple values ​​together.

#include <tuple>
#include <iostream>
#include <string>

int main() {
    // Creating a tuple: Bundles values ​​of multiple types into one
    // <int, double, std::string>: Types to store (order is important)
    std::tuple<int, double, std::string> t{42, 3.14, "hello"};
    
    // Access: std::get<index>(tuple)
    // Index must be a compile-time constant (template argument)
    int x = std::get<0>(t);        // first element (int)
    double y = std::get<1>(t);     // second element (double)
    std::string z = std::get<2>(t); // third element (string)
    
    std::cout << x << ", " << y << ", " << z << std::endl;
    
    return 0;
}

output of power:

42, 3.14, hello

1. Basic use

Create

#include <tuple>
#include <iostream>
#include <string>

int main() {
    // Method 1: Create directly (specify type)
    std::tuple<int, double> t1{42, 3.14};
    
    // Method 2: make_tuple (automatic type inference)
    // "hello" is inferred as const char*
    auto t2 = std::make_tuple(42, 3.14, "hello");
    
    // Method 3: C++17 CTAD (Class Template Argument Deduction)
    // Inferring type from constructor arguments without specifying type
    // std::string("world"): explicitly specify string type
    std::tuple t3{42, 3.14, std::string("world")};  // C++17
    
    // Size: Check the number of elements in a tuple at compile time
    // std::tuple_size_v: Returns the number of elements in a tuple (C++17)
    // decltype(t1): Extract type of 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 size: " << size1 << std::endl;
std::cout << "t2 size: " << size2 << std::endl;
    
    return 0;
}

output of power:

t1 size: 2
t2 size: 3

Access

#include <tuple>
#include <iostream>
#include <string>

int main() {
    std::tuple<int, double, std::string> t{42, 3.14, "hello"};
    
    // access by index
    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;
    
    // Access by type (when the type is unique)
    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;
}

output of power:

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) {
    // Parsing logic (example)
    int id = 123;
    std::string name = "Alice";
    bool active = true;
    
    // Return tuple: Return multiple values ​​at once
    // {id, name, active}: Create tuple with curly brace initialization
    return {id, name, active};
}

int main() {
    // C++17 structured binding: decomposing tuple into individual variables
    // auto: Automatic type inference (tuple<int, string, bool>)
    // [id, name, active]: Variable name to receive each element
    // Elements of tuple are assigned in order
    auto [id, name, active] = parseUser("data");
    
    std::cout << "ID: " << id << std::endl;
std::cout << "name: " << name << std::endl;
std::cout << "active: " << (active ? "true" : "false") << std::endl;
    
    return 0;
}

output of power:

ID: 123
Name: Alice
active: true

3. tie

Unpack to existing variable

#include <tuple>
#include <iostream>

std::tuple<int, double> getValues() {
    return {42, 3.14};
}

int main() {
    // Declare an existing variable (uninitialized)
    int x;
    double y;
    
    // std::tie: Create a tuple by binding existing variables by reference.
    // The return value of getValues() is assigned to x and y, respectively.
    // Unlike structured binding, reuse existing variables
    std::tie(x, y) = getValues();
    
    std::cout << "x: " << x << std::endl;
    std::cout << "y: " << y << std::endl;
    
    // std::ignore: Used when you want to ignore a specific element.
    // Discard the second element (double) and only accept the first.
    std::tie(x, std::ignore) = getValues();
std::cout << "x (update): " << x << std::endl;
    
    return 0;
}

output of power:

x: 42
y: 3.14
x (updated): 42

Comparison operations

#include <tuple>
#include <iostream>
#include <string>

struct Person {
    std::string name;
    int age;
    double height;
    
    // Compare with tuples
    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;
}

output of power:

p1 < p2
p1 == p3

4. Practical example

Example 1: Returning multiple values

#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 << "No real root (discriminant: " << discriminant << ")" << std::endl;
    } else {
        std::cout << "x1: " << x1 << std::endl;
        std::cout << "x2: " << x2 << std::endl;
std::cout << "Discriminant: " << discriminant << std::endl;
    }
    
    return 0;
}

output of power:

x1: 3
x2: 2
Discriminant: 1

Example 2: Sorting containers

#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}
    };
    
    // Sort descending by score
    std::sort(students.begin(), students.end(), 
         {
            return std::get<2>(a) > std::get<2>(b);
        });
    
std::cout << "=== Score order ===" << std::endl;
    for (const auto& [id, name, score] : students) {
std::cout << name << ": " << score << "point" << std::endl;
    }
    
    // Sort by score, name
    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=== Score, name order ===" << std::endl;
    for (const auto& [id, name, score] : students) {
std::cout << name << ": " << score << "point" << std::endl;
    }
    
    return 0;
}

output of power:

=== Score order ===
Bob: 92 points
David: 92 points
Alice: 85.5 points
Eve: 85.5 points
Charlie: 78.5 points

=== Score, by name ===
Bob: 92 points
David: 92 points
Alice: 85.5 points
Eve: 85.5 points
Charlie: 78.5 points

Example 3: Map key

#include <tuple>
#include <map>
#include <iostream>
#include <string>

int main() {
    // composite key
    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;
    
    // check
    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 << "student " << id << ", " << subject << ": " << score << "point" << std::endl;
    }
    
    // circuit
std::cout << "\n=== Total score ===" << std::endl;
    for (const auto& [key, score] : scores) {
        auto [id, subject] = key;
std::cout << "student " << id << ", " << subject << ": " << score << "point" << std::endl;
    }
    
    return 0;
}

output of power:

Student 1, Math: 85.5 points

=== Total Score ===
Student 1, English: 92 points
Student 1, Math: 85.5 points
Student 2, English: 88 points
Student 2, Math: 78.5 points

5. Tuple operation

comparison

#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};
    
    // comparison
    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;
}

output of power:

t1 == t2: 1
t1 == t3: 0
t1 < t3: 0
t3 < t1: 1

connect(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};
    
    // connection
    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;
}

output of power:

42, 3.14, hello, 1

6. Frequently occurring problems

Problem 1: Index

#include <tuple>
#include <iostream>

int main() {
    std::tuple<int, double, std::string> t{42, 3.14, "hello"};
    
    // ❌ Runtime index
    // int i = 0;
    // auto x = std::get<i>(t);  // error: i is not constexpr
    
    // ✅ Compile-time index
    auto x = std::get<0>(t);
    std::cout << "x: " << x << std::endl;
    
    return 0;
}

Issue 2: References

#include <tuple>
#include <iostream>

int main() {
    int x = 42;
    double y = 3.14;
    
    // ❌ Copy
    auto t1 = std::make_tuple(x, y);
    std::get<0>(t1) = 100;
    std::cout << "x: " << x << std::endl;  // 42 (unchanged)
    
    // ✅ See also
    auto t2 = std::tie(x, y);
    std::get<0>(t2) = 100;
    std::cout << "x: " << x << std::endl;  // 100 (changed)
    
    // ✅ 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;
}

output of power:

x: 42
x: 100
x: 200

Issue 3: Size

#include <tuple>
#include <iostream>

struct Point {
    int x, y;
};

int main() {
    // tuples have overhead
    std::tuple<int, int> t;
std::cout << "tuple size: " << sizeof(t) << std::endl;  // 8
    
    // struct has no overhead
    Point p;
std::cout << "struct size: " << sizeof(p) << std::endl;  // 8
    
    // complex tuple
    std::tuple<int, double, std::string> t2;
std::cout << "Complex tuple size: " << sizeof(t2) << std::endl;  // 48
    
    return 0;
}

output of power:

tuple size: 8
struct size: 8
Complex tuple size: 48

7. pair vs tuple

comparison

#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: multiple
    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;
}

output of power:

pair: 42, 3.14
tuple: 42, 3.14, hello

Selection Guide

Featurespairtuple
Number of Elements2Multiple
Access.first, .secondstd::get<N>
ReadabilityHighlow
Usekey-value, coordinatesreturn multiple values ​​
// ✅ pair: When there are two
std::pair<int, std::string> getUserInfo() {
    return {123, "Alice"};
}

// ✅ tuple: 3 or more
std::tuple<int, std::string, int, double> getUserDetails() {
    return {123, "Alice", 25, 165.5};
}

// ✅ struct: Named field (recommended)
struct User {
    int id;
    std::string name;
    int age;
    double height;
};

8. Practical example: database results

#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 << "\nAverage score: " << calculateAverage(results) << std::endl;
    
    return 0;
}

output of power:

ID	Name	Age	Score
---	----	---	-----
1	Alice	25	85.5
2	Bob	30	92
3	Charlie	28	78.5

Average score: 85.3333

organize

Key takeaways

  1. tuple: Grouping multiple values ​​(C++11)
  2. Access: std::get<N>, structured binding
  3. tie: Unpack to existing variable
  4. Compare: Lexicographic comparison
  5. Practical: Returning multiple values, temporary grouping

pair vs tuple

Featurespairtuple
Number of Elements2Multiple
Access.first, .secondstd::get<N>
ReadabilityHighlow
SizeSmallLarge
Usekey-value, coordinatesreturn multiple values ​​

Practical tips

Principle of use:

  • Returns multiple values
  • Temporary grouping
  • Comparison operations
  • Map composite key

Performance:

  • Stack Allocation
  • With overhead (padding)
  • struct recommended for small cases
  • Compile time size

caution:

  • Index is constexpr
  • Reference is tie/forward_as_tuple
  • Readability (consider struct)
  • Size overhead

Next steps

  • C++ Structured Binding
  • C++ pair
  • C++ optional

Good article to read together (internal link)

Here’s another article related to this topic.

  • C++ tuple | “The Complete Guide to Tuples”
  • C++ range-based for statement and structured binding | Modern C++ loop
  • C++ Memory Order | “Memory Order” Guide

Practical tips

These are tips that can be applied right away in practice.

Debugging tips

  • If you run into a problem, check the compiler warnings first.
  • Reproduce the problem with a simple test case

Performance Tips

  • Don’t optimize without profiling
  • Set measurable indicators first

Code review tips

  • Check in advance for areas that are frequently pointed out in code reviews.
  • Follow your team’s coding conventions

Practical checklist

This is what you need to check when applying this concept in practice.

Before writing code

  • Is this technique the best way to solve the current problem?
  • Can team members understand and maintain this code?
  • Does it meet the performance requirements?

Writing code

  • Have you resolved all compiler warnings?
  • Have you considered edge cases?
  • Is error handling appropriate?

When reviewing code

  • Is the intent of the code clear?
  • Are there enough test cases?
  • Is it documented?

Use this checklist to reduce mistakes and improve code quality.


Keywords covered in this article (related search terms)

This article will be helpful if you search for C++, tuple, pair, structured-binding, C++11, etc.


  • C++ tuple key summary |
  • C++ range-based for statement and structured binding | Modern C++ loop
  • C++ async & launch |
  • C++ Atomic Operations |
  • C++ Attributes |