> For the complete documentation index, see [llms.txt](https://deployment.gitbook.io/love/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://deployment.gitbook.io/love/whitepaper/cpp/std_variant.md).

# std::variant|C++17

**`std::variant` 是 C++17 中引入的标准库类型，用于表示多个可能类型中的一个。它类似于联合体（union），但更安全且易于使用。`std::variant` 提供了一种类型安全的方式来处理不同类型的值。**

如果你用过 C 语言，你肯定用过 union。在 C 编程语言中，union 是一种复合数据类型，允许在同一内存位置存储不同类型的数据。<mark style="color:red;">与结构体一样，union 可以有多个成员，但与结构体不同的是，union 分配的内存只够容纳其最大的成员。这就意味着，union 的所有成员共享同一个内存空间。</mark>

> <mark style="color:red;">**`std::variant`**</mark><mark style="color:red;">**适用于之前使用**</mark><mark style="color:red;">**`union`**</mark><mark style="color:red;">**的场景。**</mark>

让我们看看下面的 C 代码：

```c
#include <stdint.h>
#include <stdio.h>

union
{
    struct
    {
        uint8_t low  : 4; // 表示该位域占用4个位，这使得我们能够直接操作和访问字节的高低半部分
        uint8_t high : 4;
    }nibles;

    uint8_t bytes;
}myByte;

int main()
{
    myByte.bytes = 0xAB ;

    printf("High Nible : 0x%X\n",myByte.nibles.high);
    printf("Low Nible : 0x%X\n",myByte.nibles.low);

    return 0;
}
```

输出结果：

```c
High Nible : 0xA
Low Nible : 0xB
```

{% hint style="info" %}
&#x20;`1010`，对应十六进制的 `A`

&#x20;`1011`，对应十六进制的 `B`
{% endhint %}

如上所述，它分配的内存与联合体中最长的元素一样多。所以是 1 个字节。它将写入其中的变量放在相同的地址。这样，如果名为 bytes 的变量发生变化，那么 union 中的另一个元素，即名为 nibles 的结构也会发生变化。

但是联合体在提供便利的同时也带来了一些麻烦，看下面的代码：

```c
#include <iostream>

union MyUnion {
    int intValue;
    double doubleValue;
};

int main() {
    MyUnion myUnion;

    myUnion.doubleValue = 3.14;

    std::cout << "Integer Value: " << myUnion.intValue << "\n";

    return 0;
}
```

我们将 3.14 赋值给 double 变量。然后我们尝试访问 int 变量。结果如下:

```c
Integer Value: 1374389535
```

结果有些莫名其妙，事实上我们不应该访问int类型的那个变量，<mark style="color:red;">在 C++17 中出现的 std::variant，就是为了解决这个潜在的问题，他为我们提供了类型安全的联合体。</mark>

### 什么是`std::variant`

std::variant 是 C++17 的一个特性，它提供了一个类型安全的联合体。它是 C++ 标准库的一部分，定义在 头文件中。

在 C++ 中，union 允许你在同一内存位置存储不同类型的数据，但它不提供类型安全。std::variant 是对传统 union 的改进，因为它确保了类型安全，并为处理不同类型的值提供了一种方便的方法。

让我们先测试一下类型安全功能。

```c
#include <iostream>
#include <variant>

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

    myVariant = 42; /* Store an int */

    std::cout << std::get<double>(myVariant) << std::endl;

    return 0;
}

// g++ -std=c++17 demo.cpp -o demo
```

输出结果为：

```c
terminate called after throwing an instance of 'std::bad_variant_access'
  what():  Unexpected index
Aborted (core dumped)
```

我们创建了一个包含 int 和 double 的 std::variant 变量。我们将值 42 赋给 int 变量。然后，我们尝试访问 double 变量，得到了一个错误。就是这样！这就是类型安全！

我们再来看一个使用 std::variant 的例子。

```c
#include <iostream>
#include <variant>
#include <string>

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

    /* Assign an int to std::variant */
    myVariant = 42;

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

    /* Retrieve and use the value from std::variant */
    try {
        /* It's important to check which type is stored in std::variant before retrieving the value */
        if (std::holds_alternative<int>(myVariant))
        {
            std::cout << "Value as int: " << std::get<int>(myVariant) << std::endl;
        }
        else if (std::holds_alternative<std::string>(myVariant))
        {
            std::cout << "Value as string: " << std::get<std::string>(myVariant) << std::endl;
        }
        else
        {
            std::cout << "Unknown type!" << std::endl;
        }
    } catch (const std::bad_variant_access& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    return 0;
}
```

输出结果是：

```c
Value as string: Hello, Variant!
```

在主函数中，定义了一个名为 myVariant 的 std::variant，它可以保存一个 int 或一个 std::string。一个 int 值（本例中为 42）被赋值给 std::variant。然后一个 `std::string ("Hello, Variant!")`被分配给同一个 std::variant。这表明 std::variant 可以灵活地保存不同类型的值。try-catch 块用于处理访问存储在 std::variant 中的值时可能出现的异常。它使用 std::holds\_alternative 检查存储值的类型，然后提取并打印相应的值。如果遇到未知类型，则会打印错误信息。如果出现异常（例如试图访问错误的类型），它会捕获异常并使用 std::cerr 打印错误信息。

### `std::variant`公共成员函数 <a href="#id-1062" id="id-1062"></a>

#### 默认构造函数：

```c
std::variant<int, double, std::string> myVariant; /* Default construction */
```

#### 赋值构造函数

```c
std::variant<int, double> myVariant;
myVariant = 42; /* Assigns an int */
```

#### 使用 `index` 方法获取当前持有类型的索引

`index` 方法返回 `std::variant` 当前持有类型在模板参数列表中的索引。

```cpp
#include <variant>
#include <iostream>
#include <string>

int main() {
    std::variant<int, float, std::string> var;

    var = 42;
    std::cout << "Current index: " << var.index() << std::endl;  // 输出 0

    var = 3.14f;
    std::cout << "Current index: " << var.index() << std::endl;  // 输出 1

    var = "Hello";
    std::cout << "Current index: " << var.index() << std::endl;  // 输出 2

    return 0;
}
```

在这个示例中，`index` 方法返回了 `std::variant` 持有值的索引。索引 `0` 表示 `int` 类型，索引 `1` 表示 `float` 类型，索引 `2` 表示 `std::string` 类型。

#### 使用 `std::variant_alternative` 获取类型

`std::variant_alternative` 可以用来获取 `std::variant` 模板参数列表中指定索引处的类型。

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

int main() {
    std::variant<int, float, std::string> var;

    // 获取索引 0 对应的类型
    using T0 = std::variant_alternative<0, decltype(var)>::type;
    std::cout << "Type at index 0: " << typeid(T0).name() << std::endl;  // 输出 int

    // 获取索引 1 对应的类型
    using T1 = std::variant_alternative<1, decltype(var)>::type;
    std::cout << "Type at index 1: " << typeid(T1).name() << std::endl;  // 输出 float

    // 获取索引 2 对应的类型
    using T2 = std::variant_alternative<2, decltype(var)>::type;
    std::cout << "Type at index 2: " << typeid(T2).name() << std::endl;  // 输出 std::string

    return 0;
}
```

在这个示例中，`std::variant_alternative` 用来获取 `std::variant` 模板参数列表中指定索引处的类型。`std::variant_alternative<0, decltype(var)>::type` 获取了 `var` 的第一个类型 `int`，`std::variant_alternative<1, decltype(var)>::type` 获取了第二个类型 `float`，依此类推。

#### 综合示例

```cpp
#include <variant>
#include <iostream>
#include <string>
#include <typeinfo>

int main() {
    std::variant<int, float, std::string> var = "Hello";

    std::cout << "Current index: " << var.index() << std::endl;  // 输出 2

    if (var.index() == 0) {
        using T = std::variant_alternative<0, decltype(var)>::type;
        std::cout << "Current type: " << typeid(T).name() << " with value " << std::get<T>(var) << std::endl;
    } else if (var.index() == 1) {
        using T = std::variant_alternative<1, decltype(var)>::type;
        std::cout << "Current type: " << typeid(T).name() << " with value " << std::get<T>(var) << std::endl;
    } else if (var.index() == 2) {
        using T = std::variant_alternative<2, decltype(var)>::type;
        std::cout << "Current type: " << typeid(T).name() << " with value " << std::get<T>(var) << std::endl;
    }

    return 0;
}
```

#### &#x20;`Valueless by Exception` <a href="#id-8af1" id="id-8af1"></a>

`valueless_by_exception()` 用于查看variant变量是否赋值了。

```cpp
std::variant<int, double> myVariant;
if (myVariant.valueless_by_exception()) {
    /* Handle the case where the variant has no value */
}
```

#### `Swap` <a href="#d47e" id="d47e"></a>

用于交换两个`std::variant`的数值(**注意:需要类型一致)**

```cpp
std::variant<int, double> var1 = 42;
std::variant<int, double> var2 = 3.14;
std::swap(var1, var2);
```

#### **`empalce()`**

emplace() 使用提供的参数进行**就地构造**。

```c
std::variant<int, std::string> myVariant;
myVariant.emplace<std::string>("Hello");
```

#### `visit()`

<mark style="color:red;">`std::visit`</mark> <mark style="color:red;"></mark><mark style="color:red;">函数用于访问</mark> <mark style="color:red;"></mark><mark style="color:red;">`std::variant`</mark> <mark style="color:red;"></mark><mark style="color:red;">中存储的值。它通过接受一个访问者（visitor）对象来对</mark> <mark style="color:red;"></mark><mark style="color:red;">`std::variant`</mark> <mark style="color:red;"></mark><mark style="color:red;">中当前存储的值进行操作。访问者对象可以是一个函数对象、lambda 表达式或具有重载</mark> <mark style="color:red;"></mark><mark style="color:red;">`operator()`</mark> <mark style="color:red;"></mark><mark style="color:red;">的结构体。</mark>

`std::visit` 的基本用法包括：

1. 定义一个 `std::variant` 对象，包含多个可能的类型。
2. 创建一个访问者对象，定义对每种可能类型的操作。
3. 使用 `std::visit` 传递访问者对象和 `std::variant` 对象。

#### 详细示例

以下是详细的示例，展示如何使用 `std::visit` 来访问和操作 `std::variant` 中存储的值。

**示例 1：基本用法**

```cpp
#include <variant>
#include <iostream>
#include <string>

int main() {
    std::variant<int, float, std::string> var = "Hello, World!";

    auto visitor = [](auto&& arg) {
        std::cout << "Value: " << arg << std::endl;
    };

    std::visit(visitor, var);  // 输出: Value: Hello, World!

    var = 42;
    std::visit(visitor, var);  // 输出: Value: 42

    var = 3.14f;
    std::visit(visitor, var);  // 输出: Value: 3.14

    return 0;
}
```

**示例 2：使用结构体作为访问者**

```cpp
#include <variant>
#include <iostream>
#include <string>

struct Visitor {
    void operator()(int i) const {
        std::cout << "Integer: " << i << std::endl;
    }

    void operator()(float f) const {
        std::cout << "Float: " << f << std::endl;
    }

    void operator()(const std::string& s) const {
        std::cout << "String: " << s << std::endl;
    }
};

int main() {
    std::variant<int, float, std::string> var;

    var = 42;
    std::visit(Visitor{}, var);  // 输出: Integer: 42

    var = 3.14f;
    std::visit(Visitor{}, var);  // 输出: Float: 3.14

    var = "Hello, World!";
    std::visit(Visitor{}, var);  // 输出: String: Hello, World!

    return 0;
}
```

**示例 3：访问多个 `std::variant`**

`std::visit` 还可以用于同时访问多个 `std::variant` 对象。此时需要一个接受多个参数的访问者对象。

```cpp
#include <variant>
#include <iostream>
#include <string>

struct Visitor {
    void operator()(int i, float f) const {
        std::cout << "Integer: " << i << ", Float: " << f << std::endl;
    }

    void operator()(int i, const std::string& s) const {
        std::cout << "Integer: " << i << ", String: " << s << std::endl;
    }

    void operator()(float f, const std::string& s) const {
        std::cout << "Float: " << f << ", String: " << s << std::endl;
    }

    void operator()(const auto& a, const auto& b) const {
        std::cout << "Other types: " << a << ", " << b << std::endl;
    }
};

int main() {
    std::variant<int, float, std::string> var1 = 42;
    std::variant<int, float, std::string> var2 = "Hello, World!";

    std::visit(Visitor{}, var1, var2);  // 输出: Integer: 42, String: Hello, World!

    var2 = 3.14f;
    std::visit(Visitor{}, var1, var2);  // 输出: Integer: 42, Float: 3.14

    return 0;
}
```

### **`std::variant`的优势** <a href="#ba8b" id="ba8b"></a>

1. **类型安全**

如前所述，当访问错误的类型时，传统的联合体类型可能会导致意想不到的错误。

```c
std::variant<int, double, std::string> myVariant = 42;
// Type-safe access
std::cout << std::get<int>(myVariant) << std::endl;
```

2. **可读性更高**

明确表达了该变量有多种数据类型。

```c
std::variant<int, double, std::string> myVariant = "Hello";
// Improved readability
if (std::holds_alternative<std::string>(myVariant)) {
    std::cout << std::get<std::string>(myVariant) << std::endl;
}
```

3. **可以处理函数有多个返回值类型的情况**

```c
#include <iostream>
#include <variant>
#include <string>

std::variant<int, double, std::string> getValue(bool flag) {
    if (flag) {
        return 42;         // 返回int类型
    } else {
        return "Hello";    // 返回std::string类型
    }
}

int main() {
    std::variant<int, double, std::string> value = getValue(true);  // 调用函数并获取返回值

    if (std::holds_alternative<int>(value)) {
        std::cout << "Returned int: " << std::get<int>(value) << std::endl;
    } else if (std::holds_alternative<std::string>(value)) {
        std::cout << "Returned string: " << std::get<std::string>(value) << std::endl;
    }

    value = getValue(false);  // 再次调用函数，获取不同类型的返回值

    if (std::holds_alternative<int>(value)) {
        std::cout << "Returned int: " << std::get<int>(value) << std::endl;
    } else if (std::holds_alternative<std::string>(value)) {
        std::cout << "Returned string: " << std::get<std::string>(value) << std::endl;
    }

    return 0;
}

```

```c
#include <iostream>
#include <variant>

int main() {
    auto example = [](int x) -> std::variant<int, std::string> {
        if (x > 0) {
            return x;
        } else {
            return "Negative or Zero";
        }
    };

    auto result1 = example(10);
    auto result2 = example(-5);

    if (std::holds_alternative<int>(result1)) {
        std::cout << "Result1: " << std::get<int>(result1) << std::endl;
    } else {
        std::cout << "Result1: " << std::get<std::string>(result1) << std::endl;
    }

    if (std::holds_alternative<int>(result2)) {
        std::cout << "Result2: " << std::get<int>(result2) << std::endl;
    } else {
        std::cout << "Result2: " << std::get<std::string>(result2) << std::endl;
    }

    return 0;
```

4. **与标准库算法兼容**

```c
std::vector<std::variant<int, double, std::string>> data;
// Can use standard algorithms with std::variant
std::for_each(data.begin(), data.end(), [](auto& item) {
    std::visit([](auto&& arg){ std::cout << arg << std::endl; }, item);
});
```

### *总结*

`std::variant<T, U, ...>`代表一个多类型的容器，容器中的值是制定类型的一种，是通用的 Sum Type，对应 Rust 的`enum`。是一种类型安全的`union`，所以也叫做`tagged union`。与`union`相比有两点优势：

1. 可以存储复杂类型，而 union 只能直接存储基础的 POD 类型，对于如`std::vector`和`std::string`就等复杂类型则需要用户手动管理内存。
2. 类型安全，variant 存储了内部的类型信息，所以可以进行安全的类型转换，c++17 之前往往通过`union`+`enum`来实现相同功能。

通过使用`std::variant<T, Err>`，用户可以实现类似 Rust 的`std::result`，即在函数执行成功时返回结果，在失败时返回错误信息，上文的例子则可以改成:

```
std::variant<ReturnType, Err> func(const string& in) {
    ReturnType ret;
    if (in.size() == 0)
        return Err{"input is empty"};
    // ...
    return {ret};
}
```

需要注意的是，c++17 只提供了一个库级别的 variant 实现，没有对应的模式匹配(Pattern Matching)机制，而最接近的`std::visit`又缺少编译器的优化支持，所以在 c++17 中`std::variant`并不好用，跟 Rust 和函数式语言中出神入化的 Sum Type 还相去甚远，但是已经有许多围绕`std::variant`的提案被提交给 c++委员会探讨，包括模式匹配，`std::expected`等等。

### reference

* <https://cengizhanvarli.medium.com/std-variant-in-c-c2fc83d34efe>
* <https://mp.weixin.qq.com/s/jlM1NWRNpoOvW2qrBtxflQ>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://deployment.gitbook.io/love/whitepaper/cpp/std_variant.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
