C++ Utility Components

Essential helper types for modern C++ programming

🛠️ What are Utility Components?

C++ utility components are helper types like pair, tuple, optional, variant, and any that make code safer, more expressive, and easier to work with complex data structures.


#include <utility>
#include <optional>

std::pair<int, std::string> data = {42, "Hello"};
std::optional<int> maybe_value = 100;
                                    

Core Utility Types

👫

std::pair

Hold two related values together

std::pair<int, string> p = {1, "one"};
📦

std::tuple

Group multiple values of different types

std::tuple<int, string, double> t;

std::optional

Represent values that may or may not exist

std::optional<int> maybe = 42;
🔀

std::variant

Store one of several possible types

std::variant<int, string> v = "text";

🔹 std::pair - Two Values Together

The std::pair template is a simple, heterogeneous container for holding two related values of possibly different types. It is commonly used to return multiple values from functions, store key-value pairs (before map insertion), or represent coordinate points. Members are accessed via first and second, and structured bindings (C++17) allow clean unpacking. std::pair is foundational in the STL, underlying associative containers and many algorithms. Its simplicity and utility make it an essential tool for grouping data without defining custom structs in numerous scenarios.

#include <utility>
#include <iostream>

int main() {
    // Create a pair
    std::pair<int, std::string> student = {123, "Alice"};
    
    // Access elements
    std::cout << "ID: " << student.first << std::endl;
    std::cout << "Name: " << student.second << std::endl;
    
    // Make pair with make_pair
    auto coordinates = std::make_pair(10.5, 20.3);
    
    // Structured binding (C++17)
    auto [id, name] = student;
    std::cout << "Unpacked: " << id << ", " << name << std::endl;
    
    return 0;
}

Output:

ID: 123

Name: Alice

Unpacked: 123, Alice

🔹 std::tuple - Multiple Values

std::tuple generalizes std::pair to store a fixed-size collection of heterogeneous elements, supporting three or more values. It enables returning or passing multiple items as a single entity. Elements are accessed via std::get<index> or std::get<Type>, and C++17's structured bindings allow elegant destructuring. Tuples are invaluable in generic programming, facilitating functions that handle variable-type aggregates. They appear in metaprogramming, function wrappers, and anywhere fixed, mixed-type groupings are needed, offering type safety and compile-time flexibility beyond traditional structs.

#include <tuple>
#include <iostream>

int main() {
    // Create tuple
    std::tuple<int, std::string, double> person = {25, "Bob", 175.5};
    
    // Access with std::get
    std::cout << "Age: " << std::get<0>(person) << std::endl;
    std::cout << "Name: " << std::get<1>(person) << std::endl;
    std::cout << "Height: " << std::get<2>(person) << std::endl;
    
    // Structured binding (C++17)
    auto [age, name, height] = person;
    std::cout << name << " is " << age << " years old" << std::endl;
    
    // Make tuple
    auto data = std::make_tuple(1, "test", 3.14, true);
    
    return 0;
}

Output:

Age: 25

Name: Bob

Height: 175.5

Bob is 25 years old

🔹 std::optional - Maybe Values

std::optional (C++17) safely represents an object that may or may not contain a value, eliminating the need for sentinel values or pointers. It clearly expresses "no value" semantics, improving interface clarity for functions that can fail (like searching or parsing). Members value(), value_or(), and checks like has_value() provide safe access. optional avoids the overhead of exceptions or dynamic allocation for optional returns. It's a key tool for writing robust, self-documenting APIs that handle absence explicitly, reducing errors and improving code intent communication.

#include <optional>
#include <iostream>

std::optional<int> divide(int a, int b) {
    if (b == 0) {
        return std::nullopt;  // No value
    }
    return a / b;  // Has value
}

int main() {
    auto result1 = divide(10, 2);
    auto result2 = divide(10, 0);
    
    // Check if optional has value
    if (result1.has_value()) {
        std::cout << "Result: " << result1.value() << std::endl;
        // Or use *result1
        std::cout << "Result: " << *result1 << std::endl;
    }
    
    if (!result2) {
        std::cout << "Division by zero!" << std::endl;
    }
    
    // Use value_or for default
    int safe_result = result2.value_or(-1);
    std::cout << "Safe result: " << safe_result << std::endl;
    
    return 0;
}

Output:

Result: 5

Result: 5

Division by zero!

Safe result: -1

🔹 std::variant - One of Many Types

std::variant (C++17) is a type-safe union that holds a value of one type from a specified set of alternatives at any time. It provides value semantics, avoiding manual memory management and ensuring type safety through visitors (std::visit) and index-based queries. variant is ideal for representing state machines, parsing results, or any scenario where a value can be one of several known types. It eliminates the need for inheritance-based polymorphism in many cases, offering efficient storage and access. This makes code more predictable, easier to debug, and often more performant.

#include <variant>
#include <iostream>
#include <string>

int main() {
    // Variant can hold int, string, or double
    std::variant<int, std::string, double> data;
    
    data = 42;  // Now holds int
    std::cout << "Holds int: " << std::get<int>(data) << std::endl;
    
    data = "Hello";  // Now holds string
    std::cout << "Holds string: " << std::get<std::string>(data) << std::endl;
    
    data = 3.14;  // Now holds double
    
    // Visit pattern for type-safe access
    std::visit([](auto&& value) {
        std::cout << "Current value: " << value << std::endl;
    }, data);
    
    // Check which type is active
    if (std::holds_alternative<double>(data)) {
        std::cout << "Currently holds double" << std::endl;
    }
    
    return 0;
}

Output:

Holds int: 42

Holds string: Hello

Current value: 3.14

Currently holds double

🔹 std::any - Any Type

The std::any container is a type-safe wrapper for single values of any type, introduced in C++17. It enables you to store heterogeneous data—like integers, strings, doubles, or custom objects—in a unified way while preserving type safety. To access the stored value, you must cast it back to its original type using std::any_cast. This makes std::any ideal for scenarios requiring flexible storage, such as configuration settings, message passing, or handling unknown data types in a safe, controlled manner.

#include <any>
#include <iostream>
#include <string>

int main() {
    std::any data;
    
    data = 42;
    std::cout << "Stored int: " << std::any_cast<int>(data) << std::endl;
    
    data = std::string("Hello World");
    std::cout << "Stored string: " << std::any_cast<std::string>(data) << std::endl;
    
    data = 3.14;
    
    // Check if any has value
    if (data.has_value()) {
        std::cout << "Any has value" << std::endl;
        std::cout << "Type: " << data.type().name() << std::endl;
    }
    
    // Safe casting with try-catch
    try {
        auto value = std::any_cast<double>(data);
        std::cout << "Double value: " << value << std::endl;
    } catch (const std::bad_any_cast& e) {
        std::cout << "Bad cast: " << e.what() << std::endl;
    }
    
    return 0;
}

Output:

Stored int: 42

Stored string: Hello World

Any has value

Type: d

Double value: 3.14

🧠 Test Your Knowledge

Which utility type is best for representing a value that might not exist?