C++ Standard Library utility

In the C++ standard library,<utility>The header file contains some practical utility classes and functions, which are very useful when writing efficient and readable code.

utilityThe core value of the library lies in:

  • Providing fundamental data structures and utility functions
  • Simplifying the implementation of common programming tasks
  • Providing foundational support for other standard library components

utilityAlthough the header file is small, the tools it provides are very practical:

Components/Functions Purpose Use Case
std::pair Store two related values Functions returning multiple values, map elements
std::make_pair Conveniently creating pairs Template type deduction, simplifying code
std::swap Swap two values Algorithm implementation, sorting operations
std::move Enable move semantics Resource management, performance optimization
std::forward Perfect Forwarding Universal references, template programming

Core components explained

std::pair: key-value pair container

std::pairYesutilitypair: the most commonly used component in the library, used to combine two values into a single object.

Basic Syntax

#include <utility>

// 创建 pair 对象的基本方式
std::pair<类型1, 类型2> 变量名(值1, 值2);

Example

#include <iostream>
#include <utility>
#include <string>

int main() {
    // Method 1: Direct initialization
    std::pair<int, std::string> student1(101, "Alice");
   
    // Method 2: Using the make_pair function (recommended)
    auto student2 = std::make_pair(102, "Bob");
   
    // Method 3: Deduction guide supported since C++17
    std::pair student3(103, "Charlie");
   
    // Access the members of pair
    std::cout << "Student ID: " << student1.first << ", Name: " << student1.second << std::endl;
   
    return 0;
}

Common operations on pair

Example

#include <utility>
#include <iostream>

void pairOperations() {
    // Create a pair
    std::pair<int, double> p1(10, 3.14);
    std::pair<int, double> p2(20, 2.71);
   
    // Comparison operations
    if (p1 < p2) {
        std::cout << "p1 is less than p2" << std::endl;
    }
   
    // Assignment operation
    p1 = p2;
   
    // Memberwise assignment supported since C++11
    int a;
    double b;
    std::tie(a, b) = p1;  // Assign the value of p1 to a and b respectively
   
    // C++17 structured binding (more concise)
    auto [x, y] = p1;
    std::cout << "x = " << x << ", y = " << y << std::endl;
}

std::make_pair: convenient creation function

std::make_pairstd::make_pair: a template function that can automatically deduce types, simplifying the process of creating pairs.

Example

#include <utility>
#include <iostream>
#include <string>

void demonstrateMakePair() {
    // Automatic type deduction, no need to explicitly specify template parameters
    auto p1 = std::make_pair(42, "Hello");
    auto p2 = std::make_pair(3.14, true);
   
    // Especially convenient for use in containers
    std::vector<std::pair<int, std::string>> students;
    students.push_back(std::make_pair(101, "Alice"));
    students.push_back(std::make_pair(102, "Bob"));
   
    for (const auto& student : students) {
        std::cout << "Student ID: " << student.first
                  << ", Name: " << student.second << std::endl;
    }
}

Utility functions

std::swap: swap two values

std::swapUsed to swap the values of two objects of the same type.

Example

#include <utility>
#include <iostream>

void demonstrateSwap() {
    int a = 10, b = 20;
    std::cout << "Before swap: a = " << a << ", b = " << b << std::endl;
   
    std::swap(a, b);
    std::cout << "After swap: a = " << a << ", b = " << b << std::endl;
   
    // Can also be used for custom types (if move semantics are implemented)
    std::string str1 = "Hello", str2 = "World";
    std::swap(str1, str2);
    std::cout << "String swap: " << str1 << " " << str2 << std::endl;
}

std::move: move semantics support

std::moveUsed to convert an object into an rvalue reference, enabling move semantics.

Example

#include <utility>
#include <iostream>
#include <vector>

void demonstrateMove() {
    std::vector<int> v1 = {1, 2, 3, 4, 5};
    std::vector<int> v2;
   
    std::cout << "Before move - v1 size: " << v1.size()
              << ", v2 size: " << v2.size() << std::endl;
   
    // Use move semantics to transfer resource ownership
    v2 = std::move(v1);
   
    std::cout << "After move - v1 size: " << v1.size()
              << ", v2 size: " << v2.size() << std::endl;
   
    // v1 is now in a valid but unspecified state
    // Normally, v1 should not be used again unless reassigned
}

std::forward: perfect forwarding

std::forwardUsed to implement perfect forwarding, preserving the value category of arguments.

Example

#include <utility>
#include <iostream>

// Ordinary function - cannot preserve value category
template<typename T>
void normalFunction(T arg) {
    std::cout << "Ordinary function parameter" << std::endl;
}

// Function using perfect forwarding
template<typename T>
void perfectForwardingFunction(T&& arg) {
    // Preserve the parameter's original value category (lvalue or rvalue)
    normalFunction(std::forward<T>(arg));
}

void demonstrateForward() {
    int x = 10;
   
    // Pass an lvalue
    perfectForwardingFunction(x);
   
    // Pass an rvalue
    perfectForwardingFunction(20);
}

Integer sequence utilities (C++14)

C++14 introduced integer sequence utilities, primarily used for template metaprogramming.

std::integer_sequence

Example

#include <utility>
#include <iostream>

// Use an integer sequence to print each value in the sequence
template<typename T, T... Ints>
void print_sequence(std::integer_sequence<T, Ints...>) {
    // Use a fold expression (C++17) to print all values
    ((std::cout << Ints << " "), ...);
    std::cout << std::endl;
}

void demonstrateIntegerSequence() {
    // Create an integer sequence
    auto seq = std::integer_sequence<int, 1, 2, 3, 4, 5>();
    print_sequence(seq);
   
    // Use make_integer_sequence to generate the sequence
    auto seq2 = std::make_integer_sequence<int, 5>();
    print_sequence(seq2);  // Output: 0 1 2 3 4
}

Real-world application examples

Example 1: Function returning multiple values

Example

#include <utility>
#include <iostream>
#include <cmath>

// Function returns multiple values: computation result and error code
std::pair<double, bool> calculateSqrt(double number) {
    if (number < 0) {
        return std::make_pair(0.0, false);  // Error case
    }
    return std::make_pair(std::sqrt(number), true);  // Success case
}

void multipleReturnValues() {
    auto result1 = calculateSqrt(16.0);
    if (result1.second) {
        std::cout << "Square root: " << result1.first << std::endl;
    } else {
        std::cout << "Calculation error: cannot take the square root of a negative number" << std::endl;
    }
   
    auto result2 = calculateSqrt(-4.0);
    if (!result2.second) {
        std::cout << "Calculation error: cannot take the square root of a negative number" << std::endl;
    }
}

Case 2: Application in STL containers

Example

#include <utility>
#include <map>
#include <iostream>
#include <string>

void mapWithPair() {
    // Each element of std::map is a std::pair
    std::map<int, std::string> studentMap;
   
    // Insert key-value pair
    studentMap.insert(std::make_pair(101, "Alice"));
    studentMap.emplace(102, "Bob");  // More efficient way
   
    // Iterate over the map
    for (const auto& [id, name] : studentMap) {
        std::cout << "Student ID: " << id << ", Name: " << name << std::endl;
    }
   
    // Find element
    auto it = studentMap.find(101);
    if (it != studentMap.end()) {
        std::cout << "Found student: " << it->second << std::endl;
    }
}

Example 3: Implementing a simple dictionary

Example

#include <utility>
#include <vector>
#include <iostream>
#include <algorithm>

class SimpleDictionary {
private:
    std::vector<std::pair<std::string, std::string>> entries;
   
public:
    void addWord(const std::string& word, const std::string& meaning) {
        entries.emplace_back(word, meaning);
    }
   
    std::pair<bool, std::string> findMeaning(const std::string& word) {
        for (const auto& [w, m] : entries) {
            if (w == word) {
                return std::make_pair(true, m);
            }
        }
        return std::make_pair(false, "");
    }
   
    void printAll() {
        for (const auto& [word, meaning] : entries) {
            std::cout << word << ": " << meaning << std::endl;
        }
    }
};

void dictionaryExample() {
    SimpleDictionary dict;
    dict.addWord("apple", "a fruit");
    dict.addWord("book", "an object used for reading");
   
    auto result = dict.findMeaning("apple");
    if (result.first) {
        std::cout << "Meaning: " << result.second << std::endl;
    }
}

Best practices and considerations

1. Use auto to simplify pair creation

Example

// Recommended: use auto and make_pair
auto student = std::make_pair(101, "Alice");

// Not recommended: explicitly specifying the type (more verbose)
std::pair<int, std::string> student(101, "Alice");

2. Prefer emplace over insert

Example

std::map<int, std::string> myMap;

// Recommended: use emplace (more efficient)
myMap.emplace(1, "one");

// Not recommended: use insert (requires constructing a temporary object)
myMap.insert(std::make_pair(1, "one"));

3. Correct use of move semantics

Example

std::string createString() {
    std::string str = "a very large string";
    // Correct: use move on return
    return str;  // The compiler will automatically optimize, no need for explicit move
}

void processString(std::string str) {
    // Handle strings
}

void usageExample() {
    std::string largeStr = "very large data";
   
    // Correct: use move when passing
    processString(std::move(largeStr));
    // Note: largeStr should no longer be used
}

4. Use of structured bindings

Example

// Structured bindings supported since C++17
std::pair<int, std::string> getStudent() {
    return {101, "Alice"};
}

void structuredBindingExample() {
    // Traditional way
    auto student = getStudent();
    int id = student.first;
    std::string name = student.second;
   
    // Modern way (C++17)
    auto [id2, name2] = getStudent();  // More concise and clear
}
other extensions