Modern Utilities (C++17)
C++17 introduced several "vocabulary types" designed to replace unsafe C-style constructs (like raw null pointers, magic constants, or unsafe unions) with modern, type-safe abstractions.
1. std::optional (Safe Presence/Absence representation)
A function might fail to return a value (e.g. database query, config parsing). Previously, developers used magic values (like -1, 0, or returning a nullptr pointer). std::optional explicitly declares that a value may or may not exist, forcing safe checks.
| #include <optional>
#include <string>
#include <iostream>
// Dummy helper
bool is_valid_number(const std::string& s) {
return !s.empty() && s.find_first_not_of("0123456789") == std::string::npos;
}
// Returns an int if parsing is successful, otherwise returns std::nullopt
std::optional<int> parse_sensor_id(const std::string& input) {
if (!is_valid_number(input)) {
return std::nullopt; // Represents absence of value
}
try {
return std::stoi(input); // Return actual int
} catch (...) {
return std::nullopt;
}
}
int main() {
auto id = parse_sensor_id("105");
// 1. Explicit Check
if (id.has_value()) { // Or simply: if (id)
std::cout << "Sensor ID: " << id.value() << "\n"; // Or: *id
}
// 2. Default Fallback
// If id is empty, fallback to 0
int active_id = id.value_or(0);
return 0;
}
|
2. std::variant (Type-Safe Unions)
A standard C union can hold different types in the same memory location, but it does not know which type is currently active, and cannot safely store complex types like std::string.
std::variant is a type-safe union. It knows which type it contains, runs destructors when types change, and prevents invalid access.
| #include <variant>
#include <string>
#include <iostream>
// Define a variant that can hold an int, a double, or a string
using Payload = std::variant<int, double, std::string>;
int main() {
Payload data;
data = 42; // Currently holds int
data = "Pi in sky"; // Transferred to std::string
// 1. Safe Type Queries
if (std::holds_alternative<std::string>(data)) {
std::cout << "String: " << std::get<std::string>(data) << "\n";
}
// 2. Safe Pointer Queries
if (auto pVal = std::get_if<int>(&data)) {
std::cout << "Int: " << *pVal << "\n"; // Safely returns nullptr if type mismatch
}
return 0;
}
|
Pattern Matching with std::visit
The most powerful way to process a std::variant is using a "Visitor" object with std::visit.
| #include <variant>
#include <iostream>
#include <string>
using Payload2 = std::variant<int, double, std::string>;
struct Printer {
void operator()(int i) const { std::cout << "Integer: " << i << "\n"; }
void operator()(double d) const { std::cout << "Double: " << d << "\n"; }
void operator()(const std::string& s) const { std::cout << "String: " << s << "\n"; }
};
int main() {
Payload2 payload = 3.14;
// Calls the correct overload automatically
std::visit(Printer{}, payload);
return 0;
}
|
3. std::any (Dynamic Type Erasure)
std::any can hold a value of absolutely any copyable type. Unlike std::variant, you do not need to pre-declare the list of allowed types.
- Overhead: High. It often performs dynamic memory allocation (heap) to store values, and checking/retrieving values via std::any_cast requires runtime type validation.
| #include <any>
#include <string>
#include <iostream>
int main() {
std::any container = 100; // Stores int
container = std::string("Flexible storage"); // Stores string
try {
std::string text = std::any_cast<std::string>(container);
std::cout << text << "\n";
} catch (const std::bad_any_cast& e) {
std::cout << "Invalid cast: " << e.what() << "\n";
}
return 0;
}
|
4. Path and File System Operations (std::filesystem)
The std::filesystem library provides a robust, platform-independent API to inspect and manage file systems.
| #include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path log_dir = "/tmp/var/log/my_app";
// 1. Directory Checks & Creation
if (!fs::exists(log_dir)) {
fs::create_directories(log_dir); // Creates recursive paths if needed
}
// 2. Inspecting Files & Metadata
fs::path config_file = "config.txt";
if (fs::exists(config_file)) {
std::cout << "File size: " << fs::file_size(config_file) << " bytes\n";
}
// 3. Iterating Directories
fs::path current_dir = fs::current_path();
std::cout << "Files in " << current_dir << ":\n";
for (const auto& entry : fs::directory_iterator(current_dir)) {
std::cout << "- " << entry.path().filename().string()
<< " [" << (entry.is_directory() ? "DIR" : "FILE") << "]\n";
}
return 0;
}
|