CodeNebula
C++17stladvanced

std::variant

C++17 · Practical Modern · Compiler flag: -std=c++17

Explanation

A type-safe union that can hold one of several specified types at any time. Unlike raw unions, it tracks which type is active and throws `std::bad_variant_access` on incorrect access, eliminating a major source of undefined behavior.

Real-World Scenario

Represent different event payloads (click, keypress, resize) in a GUI framework's event system.

Quick Snippet

snippet.cpp
#include <variant>
#include <string>

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

Event e = "button_click";
std::visit([](const auto& val) {
    std::cout << val << "\n";
}, e);

Full Example

variant.cpp
// CodeNebula - C++17 Example: std::variant
// Compile: g++ -std=c++17 -Wall -Wextra -o example variant.cpp

// Demonstrates std::variant, a type-safe tagged union. Unlike C unions, variant
// tracks the active type, supports non-trivial types (strings, vectors), and
// provides visitor-based access via std::visit.

#include <iostream>
#include <variant>
#include <string>
#include <vector>
#include <cmath>

// ------------------------------------------------------------------
// Overloaded helper for visitor pattern
// ------------------------------------------------------------------
template <typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template <typename... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

// ------------------------------------------------------------------
// Type alias for a "JSON-like" value
// ------------------------------------------------------------------
using JsonValue = std::variant<std::monostate, bool, int, double, std::string>;

std::string type_name(const JsonValue& jv) {
    return std::visit(overloaded{
        [](std::monostate)      { return std::string("null"); },
        [](bool)                { return std::string("bool"); },
        [](int)                 { return std::string("int"); },
        [](double)              { return std::string("double"); },
        [](const std::string&)  { return std::string("string"); }
    }, jv);
}

int main()
{
    // ------------------------------------------------------------------
    // 1. Basic construction and access
    // ------------------------------------------------------------------
    std::cout << "=== construction ===" << std::endl;
    std::variant<int, double, std::string> v1 = 42;
    std::variant<int, double, std::string> v2 = 3.14;
    std::variant<int, double, std::string> v3 = std::string("hello");

    std::cout << "  v1 (int):    " << std::get<int>(v1) << std::endl;
    std::cout << "  v2 (double): " << std::get<double>(v2) << std::endl;
    std::cout << "  v3 (string): " << std::get<std::string>(v3) << std::endl;

    // ------------------------------------------------------------------
    // 2. index() and holds_alternative
    // ------------------------------------------------------------------
    std::cout << "\n=== type queries ===" << std::endl;
    std::cout << "  v1.index(): " << v1.index() << std::endl;
    std::cout << "  v2 holds double: " << std::boolalpha
              << std::holds_alternative<double>(v2) << std::endl;
    std::cout << "  v3 holds int: " << std::holds_alternative<int>(v3) << std::endl;

    // ------------------------------------------------------------------
    // 3. get_if: safe access via pointer (returns nullptr if wrong type)
    // ------------------------------------------------------------------
    std::cout << "\n=== get_if (safe access) ===" << std::endl;
    if (auto* p = std::get_if<int>(&v1)) {
        std::cout << "  v1 is int: " << *p << std::endl;
    }
    if (auto* p = std::get_if<double>(&v1)) {
        std::cout << "  v1 is double: " << *p << std::endl;  // not reached
    } else {
        std::cout << "  v1 is NOT double" << std::endl;
    }

    // ------------------------------------------------------------------
    // 4. Reassignment changes the active type
    // ------------------------------------------------------------------
    std::cout << "\n=== reassignment ===" << std::endl;
    v1 = std::string("now a string");
    std::cout << "  v1 is now: " << std::get<std::string>(v1) << std::endl;
    std::cout << "  v1.index(): " << v1.index() << std::endl;

    // ------------------------------------------------------------------
    // 5. std::visit with overloaded visitor
    // ------------------------------------------------------------------
    std::cout << "\n=== std::visit ===" << std::endl;
    std::variant<int, double, std::string> values[] = {10, 2.5, std::string("hi")};
    for (const auto& val : values) {
        std::visit(overloaded{
            [](int i)               { std::cout << "  int: " << i << std::endl; },
            [](double d)            { std::cout << "  double: " << d << std::endl; },
            [](const std::string& s){ std::cout << "  string: " << s << std::endl; }
        }, val);
    }

    // ------------------------------------------------------------------
    // 6. std::monostate for default-constructible variant
    // ------------------------------------------------------------------
    std::cout << "\n=== monostate (null-like) ===" << std::endl;
    JsonValue jv;  // default: monostate (first type)
    std::cout << "  default type: " << type_name(jv) << std::endl;
    jv = 42;
    std::cout << "  after = 42:   " << type_name(jv) << std::endl;
    jv = std::string("text");
    std::cout << "  after = text:  " << type_name(jv) << std::endl;

    // ------------------------------------------------------------------
    // 7. emplace: construct in-place
    // ------------------------------------------------------------------
    std::cout << "\n=== emplace ===" << std::endl;
    std::variant<std::string, std::vector<int>> sv;
    sv.emplace<std::string>(5, 'X');  // constructs string "XXXXX"
    std::cout << "  string: " << std::get<std::string>(sv) << std::endl;

    return 0;
}

/*
Expected output:

=== construction ===
  v1 (int):    42
  v2 (double): 3.14
  v3 (string): hello

=== type queries ===
  v1.index(): 0
  v2 holds double: true
  v3 holds int: false

=== get_if (safe access) ===
  v1 is int: 42
  v1 is NOT double

=== reassignment ===
  v1 is now: now a string
  v1.index(): 2

=== std::visit ===
  int: 10
  double: 2.5
  string: hi

=== monostate (null-like) ===
  default type: null
  after = 42:   int
  after = text:  string

=== emplace ===
  string: XXXXX
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example variant.cpp