8.3 variant、optional 和 any
可以使用 union
来无运行期开销地表示多个可选的类型。例如:
union U {
int i;
char* p;
};
U u;
// ...
int x = u.i; // 正确:当且仅当 u 持有整数
char* p = u.p; // 正确:当且仅当 u 持有指针
从 C 语言最早期开始,这就被当作一个不同的类型之间分时共享
内存的基本方法来使用和误用。没有编译期和运行期的检查来确保这个地址仅被用作其真实指代的类型。确保 union
成员在使用上一致,是程序员的职责,然而令人头痛的是程序员常在这个地方出错。
有经验的程序员通过将联合体封装在类中来避免问题,用类来确保正确使用。Boost 特别提供了三种这样的类型:
optional<T>
——持有T
或什么都不持有variant<T,U>
——持有T
或U
any
——持有任意类型
这些类型的巨大效用已经在 C++ 和许多其他语言中得到了证明。
委员会决定对这三种类型进行标准化。不幸的是,这三种类型的设计被分开讨论,好像它们的使用情况毫不相干一样。相对于标准库而言,直接语言支持的可 能性似乎从未被认真考虑。结果是三种标准库类型(就像它们的 Boost 祖先一样)彼此之间有很大的不同。因此,尽管这些类型的效用毋庸置疑,但它们是委员会设计的一个典型案例。试考虑:
optional<int> var1 = 7;
variant<int,string> var2 = 7;
any var3 = 7;
auto x1 = *var1 ; // 对 optional 解引用
auto x2 = get<int>(var2); // 像访问 tuple 一样访问 variant
auto x3 = any_cast<int>(var3); // 转换 any
为了提取存储的值,需要使用三种不兼容的写法之一。这对程序员来讲是一种负担。没错,有经验的程序员会习惯的,但这种非要人们去习惯的不规则性本就不该存在。
为了简化 variant
的使用,有一种访问者机制。首先我们需要一个辅助模板去定义一个重载集合:
// 简单访问的样板代码:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
模板 overloaded
真应该成为标准。只有那些熟悉变参模板(§4.3.2)和模板参数推导(§8.1)的人才会觉得它比较简单。不过,有了 overloaded
,我就能根据变体的类型来构造出分支:
using var_t = std::variant<int, long, double, std::string>; // variant 类型
// 简单访问的样板代码:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
void use()
{
std::vector<var_t> vec = {10, 20L, 30.40, "hello"};
for (auto& var : vec) {
std::visit (overloaded {
[](auto arg) { cout << arg << '\n'; }, // 处理整数类型
[](double arg) { cout << "double : " << arg << '\n'; },
[](const std::string& arg) { cout << "\"" << arg << "\"\n"; },
}, var);
}
}
毋庸置疑,variant
和它的伙伴们解决了一个重要问题,但其方式并不优雅。或许将来的工作能减轻接口不一致上的困惑,从而让人能专注于真正需要区分的地方。同时,应该让更多的 C++ 同仁去使用这些新的类型,从而消除 union
经年累月带来的老问题。
我认为这三种可辨识 union
的变体只是权宜之计。要解决 union
的问题,函数式编程风格的模式匹配要优雅、通用得多,也潜在地更为高效。在 2014 年 11 月在伊利诺伊大学厄巴纳——香槟分校举行的会议上,我发表了关于模式匹配相关设计问题的演讲 [Solodkyy et al. 2014],部分内容基于我同得州农工大学的 Yuriy Solodkyy 和 Gabriel Dos Reis 合作的研究 [Solodkyy et al. 2013]。我们有一个库的实现,它的性能和函数式编程语言相若,尽管它没有和编译器进行集成。这个库既能应对包含多个可选类型的封闭集合(代数类型), 也能应对开放集合(类层次结构)。我们的目的之一是消除对访问者模式的使用 [Gamma et al. 1994]。然而,我们没有一种能让人普遍接受的语法。我演讲的目的是提高人们的兴趣,并设定长期的目标。人们对此很感兴趣。在 C++17 完成后,工作就开始了 [Murzin et al. 2019, 2020]。或许模式匹配能加入到 C++23 中(§11.5)。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论