Skip to content

Latest commit

 

History

History
191 lines (139 loc) · 6.39 KB

std_variant.md

File metadata and controls

191 lines (139 loc) · 6.39 KB

Explanation of std::variant

std::variant is a type-safe union available in C++17 and later. It allows you to store a value that could be one of several different types, but only one type at a time. Unlike traditional unions in C++, std::variant provides type safety by ensuring that only one type is active at any given time and by providing ways to query and retrieve the currently active type.

Simple Example of std::variant

Let's say you want to store either an int or a std::string in a single variable. Using std::variant, you can define such a variable and safely use it.

Here’s a simple example:

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

int main() {
    // Define a variant that can hold either an int or a std::string
    std::variant<int, std::string> myVariant;

    // Assign an int to the variant
    myVariant = 42;
    
    // Check the type and print the value
    if (std::holds_alternative<int>(myVariant)) {
        std::cout << "Integer: " << std::get<int>(myVariant) << std::endl;
    }

    // Assign a string to the variant
    myVariant = std::string("Hello, Variant!");

    // Check the type and print the value
    if (std::holds_alternative<std::string>(myVariant)) {
        std::cout << "String: " << std::get<std::string>(myVariant) << std::endl;
    }

    return 0;
}

Explanation of the Example

  1. Declaration:

    std::variant<int, std::string> myVariant;

    This declares a variable myVariant that can hold either an int or a std::string.

  2. Assigning an int:

    myVariant = 42;

    Here, the variant is assigned an integer value 42.

  3. Checking the type and getting the value:

    if (std::holds_alternative<int>(myVariant)) {
        std::cout << "Integer: " << std::get<int>(myVariant) << std::endl;
    }

    This checks if the variant currently holds an int using std::holds_alternative<int>. If true, it retrieves the value using std::get<int>.

  4. Assigning a std::string:

    myVariant = std::string("Hello, Variant!");

    The variant is now assigned a string value.

  5. Checking the type and getting the value (string):

    if (std::holds_alternative<std::string>(myVariant)) {
        std::cout << "String: " << std::get<std::string>(myVariant) << std::endl;
    }

    Similarly, this checks if the variant holds a std::string and retrieves the value.

Real-World Example 1: Handling Different Types of Configuration Values

Consider a configuration system where values can be of different types, such as integers, floating-point numbers, strings, or boolean values. Using std::variant allows you to store these different types in a single container.

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

using ConfigValue = std::variant<int, double, std::string, bool>;

void printConfigValue(const ConfigValue& value) {
    std::visit([](auto&& arg) { std::cout << arg << std::endl; }, value);
}

int main() {
    std::unordered_map<std::string, ConfigValue> config;

    config["max_connections"] = 100;
    config["timeout"] = 30.5;
    config["username"] = std::string("admin");
    config["debug_mode"] = true;

    for (const auto& [key, value] : config) {
        std::cout << key << ": ";
        printConfigValue(value);
    }

    return 0;
}

Explanation

  • Using std::variant for Configuration Values:

    • The ConfigValue type is defined as a std::variant that can hold an int, double, std::string, or bool.
    • The configuration values are stored in a std::unordered_map where each key maps to a ConfigValue.
  • Printing Configuration Values:

    • The printConfigValue function uses std::visit to handle each possible type in the variant and print its value.

Real-World Example 2: Representing Different Types of Shapes

In a graphics or CAD application, you might have different types of shapes (e.g., circles, rectangles, triangles) but want to store them in a single container for processing.

#include <iostream>
#include <variant>
#include <vector>

struct Circle {
    double radius;
};

struct Rectangle {
    double width, height;
};

struct Triangle {
    double base, height;
};

using Shape = std::variant<Circle, Rectangle, Triangle>;

void printShape(const Shape& shape) {
    std::visit([](auto&& arg) {
        using T = std::decay_t<decltype(arg)>;
        if constexpr (std::is_same_v<T, Circle>) {
            std::cout << "Circle with radius: " << arg.radius << std::endl;
        } else if constexpr (std::is_same_v<T, Rectangle>) {
            std::cout << "Rectangle with width: " << arg.width << " and height: " << arg.height << std::endl;
        } else if constexpr (std::is_same_v<T, Triangle>) {
            std::cout << "Triangle with base: " << arg.base << " and height: " << arg.height << std::endl;
        }
    }, shape);
}

int main() {
    std::vector<Shape> shapes = {
        Circle{5.0},
        Rectangle{3.0, 4.0},
        Triangle{6.0, 2.5}
    };

    for (const auto& shape : shapes) {
        printShape(shape);
    }

    return 0;
}

Explanation

  • Using std::variant for Shapes:

    • The Shape type is defined as a std::variant that can hold a Circle, Rectangle, or Triangle.
  • Printing Shape Information:

    • The printShape function uses std::visit with a lambda to check the active type in the variant and print the corresponding shape information.

Benefits of std::variant

  • Type Safety: Ensures that only one type is active and that the operations are valid for that type.
  • Simplified Code: Eliminates the need for manual type management and type checks.
  • Extensibility: Easily add more types to the variant without changing the existing logic significantly.
  • Ease of Use: Functions like std::visit provide a clean way to handle different types without repetitive code.

In summary, std::variant is a powerful tool for managing variables that can hold multiple types, ensuring type safety and making the code easier to maintain and extend.

std::variant is particularly useful in scenarios where a variable or a function needs to work with multiple types of data, but only one type at a time. It ensures type safety and eliminates the need for manual type management that comes with traditional unions or void* pointers. Here are some real-world examples where std::variant proves beneficial: