Sobes.tech
Junior

What can std::optional be used for in C++?

sobes.tech AI

Answer from AI

std::optional in C++17 is used to represent a value that may or may not be present.

Main uses:

  • Returning from functions that may fail: Instead of returning a special "null" value or using output parameters, a function can return std::optional<T>, where T is the type of the successful result. If the operation succeeds, optional contains a value; if not, it is empty.
    #include <optional>
    #include <string>
    
    std::optional<std::string> find_user_by_id(int id) {
        if (id == 123) {
            return "Alice"; // User found
        } else {
            return std::nullopt; // User not found
        }
    }
    
  • Passing optional arguments to functions: A function can accept std::optional<T> as a parameter, indicating that the value of this parameter may or may not be provided.
    #include <optional>
    
    void process_data(int value, std::optional<int> optional_config = std::nullopt) {
        if (optional_config) {
            // Using optional configuration
            int config = optional_config.value();
            // ...
        } else {
            // Using default value or another path
            // ...
        }
    }
    
  • Representing missing states in data structures: In fields of structs or classes where the value may be unknown or not applicable.
    #include <optional>
    #include <string>
    
    struct UserDetails {
        std::string name;
        std::optional<int> age; // Age may be unknown
    };
    
  • Difference from nullptr pointers: std::optional explicitly expresses the semantics of an optional value, whereas a pointer can be used for ownership or referencing an object, and nullptr is just one case. std::optional also avoids overhead associated with dynamic memory allocation if the object is created directly inside it.

Key features:

  • Explicitness: Clearly indicates that a value may be absent.
  • Safety: Access methods (e.g., .value()) can throw exceptions if the value is absent, preventing undefined behavior. It's safer to use .has_value() or * and -> operators after checking.
  • Efficiency: The value is stored directly in optional (small optimization at the stack/ownership level) or nearby, without overhead for dynamic memory allocation for the value itself, unless it is a very large object or requires polymorphic behavior.

Example usage:

#include <optional>
#include <string>
#include <iostream>

std::optional<std::string> get_setting(const std::string& key) {
    if (key == "timeout") {
        return "60";
    } else {
        return std::nullopt; // Setting not found
    }
}

int main() {
    auto timeout_setting = get_setting("timeout");

    if (timeout_setting.has_value()) {
        std::cout << "Timeout: " << timeout_setting.value() << std::endl;
    } else {
        std::cout << "Timeout setting not found" << std::endl;
    }

    auto user_setting = get_setting("user");

    // Alternative access using * and check
    if (user_setting) {
        std::cout << "User: " << *user_setting << std::endl;
    } else {
        std::cout << "User setting not found" << std::endl;
    }

    // Using value_or to provide a default value
    std::cout << "Default timeout: " << get_setting("non_existent").value_or("30") << std::endl;

    return 0;
}