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
| Features | pair | tuple |
|---|---|---|
| Number of Elements | 2 | Multiple |
| Access | .first, .second | std::get<N> |
| Readability | High | low |
| Use | key-value, coordinates | return 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
- tuple: Grouping multiple values (C++11)
- Access:
std::get<N>, structured binding - tie: Unpack to existing variable
- Compare: Lexicographic comparison
- Practical: Returning multiple values, temporary grouping
pair vs tuple
| Features | pair | tuple |
|---|---|---|
| Number of Elements | 2 | Multiple |
| Access | .first, .second | std::get<N> |
| Readability | High | low |
| Size | Small | Large |
| Use | key-value, coordinates | return 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.
Related articles
- C++ tuple key summary |
- C++ range-based for statement and structured binding | Modern C++ loop
- C++ async & launch |
- C++ Atomic Operations |
- C++ Attributes |