C++ tuple detailed Complete Guide | 'Tuple' guide
이 글의 핵심
C++ tuple: basic usage, structured binding (C++17), principles, code, and practical applications.
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:
Run the following commands:
=== 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:
Run the following commands:
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:
Run the following commands:
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
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
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. C++ tuple: basic usage, structured binding (C++17), principles, code, and practical applications.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ Chrono Literals | ‘시간 리터럴’ 가이드
- C++ Algorithm Heap | ‘힙 알고리즘’ 가이드
- C++ Allocator | ‘메모리 할당자’ 커스터마이징 가이드
Keywords Covered in This Article (Related Search Terms)
This article covers C++, tuple, pair, structured-binding, C++11.