取自 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...>;
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...>中的基类对象一一初始化