C++学习笔记:variant及其overload模式

本文最后更新于:2023年3月15日 中午

1 std::variant

std::variant是C++17引入的一个替代union的容器。

一个std::variant的实例包含它的某一个类型的值,这个值不能是引用、C数组、void。std::variant默认初始化为其中的第一个类型,因此,第一个类型必须有一个默认的构造函数。下面是一个来自std::variant - cppreference.com的例子:

1
2
3
4
5
6
7
8
9
10
#include <variant>

int main() {
std::variant<int, float> v, w;
v = 12;
int i = std::get<int>(v);//按类型取值
w = std::get<int>(v);
w = std::get<0>(v);//按索引取值
w = v;
}

2 std::visit

std::visit允许你对于容器中的 std::variant使用 visitor 模式。visitor必须是可以调用的。一般可以调用的都是函数,函数对象或者 lambda表达式。在下面的例子中使用lambda表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// visitVariants.cpp

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

int main() {
//创建一个std::variant类型的vector容器并且初始化
std::vector<std::variant<char, long, float, int, double, long long>> vecVariant = {5, '2', 5.4, 100ll, 2011l, 3.5f,
2017};
//遍历,打印每个值
for (auto &v: vecVariant) {
std::visit([](auto arg) { std::cout << arg << " "; }, v); // 2
}
std::cout << '\n';
//遍历,打印每个值得类型
for (auto &v: vecVariant) {
std::visit([](auto arg) { std::cout << typeid(arg).name() << " "; }, v); // 3
}
std::cout << "\n\n";
}

运行结果如下:

在上述代码中使用了泛型lambda(generic lambda)表达式,但是我们想针对每一个数据类型使用不同的 lambda 表达式,这需要用到 overload 模式。

3 overload模式

使用 overload 模式,可以访问单独的 lambda 表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <string>

using namespace std;

template<class... Ts>
struct overloaded : Ts ... {
using Ts::operator()...;
};
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main() {
overloaded s{
[](int) { cout << "int" << endl; },
[](double) { cout << "double" << endl; },
[](string) { cout << "string" << endl; },
};
s(1); // int
s(1.0); // double
s("1"); // string
}

程序输出结果如下:

  • template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };:这是一个类模板的声明。

    • template<class... Ts>:overloaded类的模板参数为可变长的参数包 Ts。假设 Ts 包含T1,T2,...,TN,那么这一句声明可以展开为:template<class T1, class T2, ... , class TN>
    • struct overloaded : Ts...:overloaded 类的基类为参数包 Ts 内所有的参数类型。假设 Ts 包含 T1, T2, … , TN,那么这一句声明可以展开为:struct overloaded : T1, T2, …, TN。
    • using Ts::operator()...;:这是一个变长 using 声明。假设 Ts 包含 T1, T2, … , TN,那么这一句声明可以展开为:using T1::operator(), T2::operator(), ..., TN::operator();也就是说,overloaded 类的基类即参数包 Ts 内所有的参数类型的函数调用操作符均被 overloaded 类引入了自己的作用域。
  • template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;:这是一个自动推断向导,用于帮助编译器根据overloaded 构造器参数的类型来推导 overloaded 的模板参数类型。

    这个自动推断向导告诉编译器,如果 overloaded 构造器所有参数的类型的集合为 Ts,那么 overloaded 的模板参数类型就是 Ts 所包含的所有类型。

    也就是说如果表达式a1, a2, …, an的类型分别为T1, T2, …, TN,
    那么构造器表达式overloaded {a1, a2, ..., an} 的类型就是 overloaded<T1, T2, ..., TN>
    *overloaded s{
    [](int){cout << "int" << endl;},
    [](double){cout << "double" << endl;},
    [](string){cout << "string" << endl;},
    };
    overloaded 类的实例 s 的构造器包含 3 个 lambda 参数,也可以看作 3 个各自包含一个 operator() 的函数对象。

    根据 overloaded 类的定义,s 对象将继承这 3 个 lambda(函数对象)的 operator() ,也就是说这3个lambda的 operator() 即函数体在 s 对象内部形成重载关系。

    根据 overloaded 类的自动推断向导,s 对象的类型为overloaded<T1, T2, T3>,其中 T1, T2, T3为3个lambda参数的类型。

使用overload模式访问std::variant

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <variant>
#include <string>
#include <iostream>

template<class... Ts>
struct overload : Ts ... {
using Ts::operator()...;
};
template<class... Ts> overload(Ts...) -> overload<Ts...>;

int main() {
std::variant<int, float, std::string> intFloatString{"Hello"};
std::visit(
overload{
[](const int &i) { std::cout << "int: " << i; },
[](const float &f) { std::cout << "float: " << f; },
[](const std::string &s) { std::cout << "string: " << s; }
},
intFloatString
);
}

输出结果如下:

如果没有overload,则必须为调用operator()写一个具有三个重载的单独的类或结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <variant>
#include <string>
#include <iostream>

struct PrintVisitor {
void operator()(int &i) const {
std::cout << "int: " << i;
}

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

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

int main() {
std::variant<int, float, std::string> intFloatString{"Hello"};
std::visit(PrintVisitor(), intFloatString);
}

参考文章:

Visiting a std::variant with the Overload Pattern - ModernesCpp.com

(7条消息) The overload Pattern_Jeff_的博客-CSDN博客


C++学习笔记:variant及其overload模式
https://summersong.top/post/6c683693.html
作者
SummerSong
发布于
2022年9月1日
更新于
2023年3月15日
许可协议