第 18.15 節

std::variant

0瀏覽次數0訪問次數--跳出率--平均停留

What problem does this section solve?

Sometimes a variable needs to store a value that could be an int, a string, or a double. The traditional approach is union (in C), but it's not type-safe — you don't know which type is currently stored, and if you access it incorrectly, it crashes.

std::variant is a type-safe union that can store one of multiple types and knows which type it currently holds.

What is this feature?

std::variant<T1, T2, ...> is a type-safe union introduced in C++17. At any given time, it stores a value of only one of its contained types. When you access it, the compiler performs checks to ensure you don't accidentally retrieve a value of the wrong type.

C++ standard version

C++17

Required header files

#include <variant>

Basic Syntax

std::variant<int, double, std::string> v;

v = 42;                      // 存 int
v = 3.14;                    // 存 double
v = std::string("hello");   // 存 string

// 访问方式 1:std::get<T>(v) —— 类型不对抛异常
int n = std::get<int>(v);

// 访问方式 2:std::get_if<T>(&v) —— 类型不对返回 nullptr
if (auto* p = std::get_if<int>(&v)) { ... }

// 访问方式 3:std::visit —— 用 visitor 模式处理所有可能的类型
std::visit([](auto&& val) { ... }, v);

// 查询当前存储的类型的索引
size_t idx = v.index();  // 0-based

Common Usage

OperationExplanation
v = value;Assignment (Automatic Type Switching)
v.emplace<T>(args...)In-place construction
std::get<T>(v)Get value (throws std::bad_variant_access if the type is incorrect)
std::get_if<T>(&v)Safe retrieval (returns nullptr if type does not match)
v.index()Return the current type index (0-based)
std::visit(visitor, v)Use the visitor pattern to handle
std::holds_alternative<T>(v)Check if holding type T

Example code

Example 1: variant basic usage—storing different types of values

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

int main()
{
    // v 可以存 int、double 或 string
    std::variant<int, double, std::string> v;

    v = 42;
    std::cout << "int: " << std::get<int>(v) << "\n";

    v = 3.14;
    std::cout << "double: " << std::get<double>(v) << "\n";

    v = std::string("hello");
    std::cout << "string: " << std::get<std::string>(v) << "\n";

    // 查看当前类型索引
    std::cout << "current index: " << v.index() << "\n";  // 2 (string)

    return 0;
}

Results

int: 42
double: 3.14
string: hello
current index: 2

Example 2: Building on Example 1, using get_if for safe access

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

void print_value(const std::variant<int, double, std::string>& v)
{
    // 安全方式:逐个尝试,get_if 返回指针
    if (auto* p = std::get_if<int>(&v))
    {
        std::cout << "int: " << *p << "\n";
    }
    else if (auto* p = std::get_if<double>(&v))
    {
        std::cout << "double: " << *p << "\n";
    }
    else if (auto* p = std::get_if<std::string>(&v))
    {
        std::cout << "string: " << *p << "\n";
    }
}

int main()
{
    std::variant<int, double, std::string> v;

    v = 42;
    print_value(v);

    v = 3.14159;
    print_value(v);

    v = std::string("C++17");
    print_value(v);

    return 0;
}

Results

int: 42
double: 3.14159
string: C++17

Example 3:Building on Example 2, use std::visit to handle all types

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

int main()
{
    std::variant<int, double, std::string> v;

    // std::visit 配合泛型 lambda 优雅处理所有类型
    auto printer = [](const auto& val) {
        std::cout << "value: " << val << "\n";
    };

    v = 42;
    std::visit(printer, v);

    v = 2.718;
    std::visit(printer, v);

    v = std::string("hello variant");
    std::visit(printer, v);

    // 也可以返回不同类型的值
    auto to_double = [](const auto& val) -> double {
        if constexpr (std::is_same_v<std::decay_t<decltype(val)>, std::string>)
        {
            return 0.0;  // string 不能转 double
        }
        else
        {
            return static_cast<double>(val);
        }
    };

    v = 10;
    std::cout << "to_double: " << std::visit(to_double, v) << "\n";

    return 0;
}

Results

value: 42
value: 2.718
value: hello variant
to_double: 10

Example 4: Building on Example 3, using variant to represent message types

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

// 定义消息类型
struct TextMessage { std::string text; };
struct NumberMessage { int number; };
struct QuitMessage {};

using Message = std::variant<TextMessage, NumberMessage, QuitMessage>;

// 处理消息的 visitor
struct MessageHandler
{
    void operator()(const TextMessage& msg) const
    {
        std::cout << "Text: " << msg.text << "\n";
    }
    void operator()(const NumberMessage& msg) const
    {
        std::cout << "Number: " << msg.number << "\n";
    }
    void operator()(const QuitMessage&) const
    {
        std::cout << "Quit!\n";
    }
};

int main()
{
    Message msg;

    msg = TextMessage{"Hello World"};
    std::visit(MessageHandler{}, msg);

    msg = NumberMessage{42};
    std::visit(MessageHandler{}, msg);

    msg = QuitMessage{};
    std::visit(MessageHandler{}, msg);

    return 0;
}

Results

Text: Hello World
Number: 42
Quit!

runtime results

See the "running results" for each example above.

Key syntax explanation in the example

|Here is the translation of the provided Simplified Chinese Markdown fragment into natural American English, following all specified rules.


ExampleDiscusses whatNewly emerged syntaxWhy write it this wayPrecautions
Example 1Basic Assignment and getstd::variant<int, double, string>std::get<T>(v)A variant is type-safe and automatically switches its type when assigned.An incorrect type will throw an exception.
Example 2get_if safe accessstd::get_if<T>(&v)returns pointer, if type mismatch returns nullptrMore secure than get, recommended
Example 3visitor patternstd::visit(lambda, v)visit forces coverage of all types, making it the optimal way to access a variant.Generic lambdas + std::visit is the most concise combination.
Example 4Message Distribution Patternstruct visitor + variantUsing variant and visitor for type-safe message handlingVisitors must provide an operator() for each type.

Variant is suitable for "one of a limited number of types."

variant isn't meant to replace all inheritance and polymorphism. It's best suited for scenarios where you have a limited set of type kinds and you want the compiler to remind you to handle all the cases.

SceneRecommendation
Messages are only of three types: Text, Number, and Quit.std::variant
States only consist of a few categories: Idle / Running / Error.std::variant
The parsing result could be of type int, double, or string.std::variant
There are many types and they require runtime plugin extensions.Inheritance + Virtual Functions
All objects share a common interface.Polymorphic interfaces are more natural.

Example 5: Using variant to represent a state machine

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

struct Idle {};
struct Running
{
    int task_id;
};
struct Error
{
    std::string message;
};

// variant 表示一个变量可以在多个候选类型中保存其中一种。
using State = std::variant<Idle, Running, Error>;

void print_state(const State& state)
{
    // visit 会根据 variant 当前保存的类型调用对应处理逻辑。
    std::visit([](const auto& s) {
        using T = std::decay_t<decltype(s)>;

        if constexpr (std::is_same_v<T, Idle>)
        {
            std::cout << "state: idle\n";
        }
        else if constexpr (std::is_same_v<T, Running>)
        {
            std::cout << "state: running task " << s.task_id << "\n";
        }
        else if constexpr (std::is_same_v<T, Error>)
        {
            std::cout << "state: error " << s.message << "\n";
        }
    }, state);
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    State state = Idle{};
    print_state(state);

    state = Running{42};
    print_state(state);

    state = Error{"motor timeout"};
    print_state(state);

    return 0;
}

Results

state: idle
state: running task 42
state: error motor timeout

Here, the state can only ever be one of three. Rather than adding extra fields with int state_code, variant can place the data needed for each state within its corresponding type, reducing issues like "reading the running field while in an error state."

Common Errors

Error 1: Incorrect type used with get, causing exceptions to be thrown.

std::variant<int, double> v = 42;
std::cout << std::get<double>(v);  // ❌ 抛出 std::bad_variant_access!

Correct approach: First use std::holds_alternative<double>(v) to check, or use std::get_if.

Error 2: Default construction when variant has no default type

std::variant<int, std::string> v;  // 默认构造第一个类型的默认值(int = 0)

This situation is valid, but if the first type lacks a default constructor, compilation fails.

Error 3: Visitor of visit has not covered all types

struct Visitor {
    void operator()(int) {}
    // 缺少 double 和 string 的 operator()
};
std::variant<int, double, std::string> v;
std::visit(Visitor{}, v);  // ❌ 编译错误!

Correct approach: The visitor for visit must provide operator() for all types in the variant, or use a generic lambda.

使用建议

  • 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
  • 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
  • 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
  • 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
  • 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。

如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。

  1. 替代 union:type-safe variant, knows what it currently holds.
  2. Using std::visit + generic lambda is the most concise way to access.
  3. When you need to "know the current type": return a pointer, safely and efficiently.
  4. Implementing message/event dispatching with variant + visit: A rudimentary form of pattern matching.
  5. The size of a variant is the largest among all types + the index field: Avoid storing many large types.
  6. variant is clearer when type kinds are limited: If types need to be extended with plugins, inheritance and virtual functions are usually more appropriate.

Summary

  • std::variant<T1, T2, ...> is a type-safe union.
  • std::get<T>(v) direct access (unsafe), std::get_if<T>(&v) returns pointer (safe).
  • std::visit(visitor, v) is the most recommended approach to force coverage for all types.
  • Suitable for scenarios like message distribution, optional configuration, and state machines.
音乐页