C++中的visit

取自 A Tour of C++

cpp 中为了方便处理std::variant,引入了访问者模式,这个函数叫std::visit,它接受两个参数,一个是访问者,另外一个是std::variant类型的变量v,其中访问者封装了如何处理解开v后遇见的不同类型的逻辑,std::variant会根据v当前存储的类型,调用访问者对应的重载

访问者的一种写法是泛型 lambda(auto&& arg),偷懒的话甚至可以写成[](auto&& arg) { std::cout << arg; }

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

auto visitor = [](auto&& arg) {
using T = std::decay_t<decltype(arg)>;

if constexpr (std::is_same_v<T, int>) {
std::cout << "Int: " << arg << '\n';
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "Double: " << arg << '\n';
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "String: " << arg << '\n';
} else {
std::cout << "Unknown type!" << '\n';
}
};

int main() {
using MyVariant = std::variant<int, double, std::string>;
MyVariant v1 = 42;
MyVariant v2 = 3.14159;
MyVariant v3 = std::string("Hello C++ Variant");

std::visit(visitor, v1);
std::visit(visitor, v2);
std::visit(visitor, v3);
}

另外一种写法是overloaded functors,好处是编译器就能检查访问者是否处理了所有可能的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <variant>
#include <string>

template <typename... Ts>
struct Overloaded : Ts... {
using Ts::operator()...;
};

template <typename... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>; //CTAD

int main() {
using MyVariant = std::variant<int, double, std::string>;
MyVariant v1 = 42;
MyVariant v2 = 3.14159;
MyVariant v3 = std::string("Hello C++ Variant");

auto visitor = Overloaded{
[](int i) {
std::cout << "Int: " << i << '\n';
},
[](double d) {
std::cout << "Double: " << d << '\n';
},
[](const std::string& s) {
std::cout << "String: " << s << '\n';
}
};
std::visit(visitor, v1);
std::visit(visitor, v2);
std::visit(visitor, v3);
}

这里面CTAD那两行代码,我想了好久,总结下来是,当检测到Overloaded类型有聚合初始化行为时,发现能够匹配得上,就可以补齐Overloaded的模板参数,推导为Overloaded<Ts...>类型,然后用的是几个 lambda 表达式来初始化,而 lambda 表达式实际上是functor,就用这几个functor分别给Overladed<Ts...>中的基类对象一一初始化