专业标准::可选

Specializing std::optional

本文关键字:可选 专业标准      更新时间:2023-10-16

是否可以针对用户定义的类型专门化std::optional?如果没有,现在向标准提出这个建议是否为时已晚?

我的用例是一个类似整数的类,它表示范围内的值。例如,您可能有一个介于 [0, 10] 范围内的整数。我的许多应用程序甚至对单个字节的开销都很敏感,因此由于额外的bool,我无法使用非专用std::optional。但是,对于范围小于其基础类型的整数,std::optional的专用化将微不足道。我们可以简单地存储11我示例中的值。这应该不会在非可选值上提供空间或时间开销。

我可以在namespace std中创建此专业化吗?

17.6.4.2.1 [namespace.std]/1 中的一般规则适用:

仅当声明依赖于用户定义类型并且专用化满足原始模板的标准库要求且未显式时,程序才能将任何标准库模板的模板专用化添加到命名空间std 禁止。

所以我会说这是允许的。

:注: optional不会成为 C++14 标准的一部分,但它将包含在关于库基础知识的单独技术规范中,因此如果我的解释错误,有时间更改规则。

如果您正在寻找一个有效地将值和"无值"标志打包到一个内存位置的库,我建议您查看compact_optional。它正是这样做的。

它不专门针对boost::optionalstd::experimental::optional,但它可以将它们包装在里面,为您提供统一的界面,并在可能的情况下进行优化,并在需要时回退到"经典"可选。

我问过同样的事情,关于专门optional<bool>optional<tribool>等例子,只使用一个字节。虽然没有讨论做这些事情的"合法性",但我确实认为,理论上不应该允许人们专门化optional<T>,而不是例如:哈希(这是明确允许的(。

我没有随身携带日志,但部分理由是接口将对数据的访问视为对指针或引用的访问,这意味着如果您在内部使用不同的数据结构,则访问的某些不变量可能会更改; 更不用说为接口提供对数据的访问权限可能需要类似 reinterpret_cast<(some_reference_type)> 的东西。例如,使用 uint8_t 来存储可选布尔值会对optional<bool>接口施加一些额外的要求,这些要求与 optional<T> 的接口不同。例如,operator*的返回类型应该是什么?

基本上,我猜这个想法是再次避免整个vector<bool>惨败

在您的示例中,它可能不太糟糕,因为访问类型仍然是your_integer_type&(或指针(。但在这种情况下,简单地设计整数类型以允许"僵尸"或"未确定"值,而不是依靠optional<>来完成工作,并具有额外的开销和要求,可能是最安全的选择。

轻松选择加入以节省空间

我已经决定这是一件有用的事情,但完全专业化的工作比必要的要多一点(例如,正确operator=(。

我已经在 Boost 邮件列表中发布了一种简化专用化任务的方法,尤其是当您只想专门化类模板的某些实例化时。

http://boost.2283326.n4.nabble.com/optional-Specializing-optional-to-save-space-td4680362.html

我目前的界面涉及一种特殊的标签类型,用于"解锁"对特定功能的访问。我创造性地将这种类型命名为optional_tag。只有optional才能构建optional_tag。对于选择加入空间高效表示形式的类型,它需要以下成员函数:

  • T(optional_tag)构造一个未初始化的值
  • initialize(optional_tag, Args && ...)构造一个对象,当可能已经存在一个对象时
  • uninitialize(optional_tag)销毁包含的对象
  • is_initialized(optional_tag)检查对象当前是否处于初始化状态

通过始终要求 optional_tag 参数,我们不会限制任何函数签名。这就是为什么,例如,我们不能使用 operator bool() 作为测试,因为类型可能出于其他原因需要该运算符。

与其他一些可能的实现方法相比,它的一个优点是,您可以使其与任何可以自然支持这种状态的类型一起工作。它不会添加任何要求,例如具有移动构造函数。

您可以在以下位置查看该想法的完整代码实现

https://bitbucket.org/davidstone/bounded_integer/src/8c5e7567f0d8b3a04cc98142060a020b58b2a00f/bounded_integer/detail/optional/optional.hpp?at=default&fileviewer=file-view-default

对于使用该专业化的类:

https://bitbucket.org/davidstone/bounded_integer/src/8c5e7567f0d8b3a04cc98142060a020b58b2a00f/bounded_integer/detail/class.hpp?at=default&fileviewer=file-view-default

(第 220 行至第 242 行(

另一种方法

这与我以前的实现形成鲜明对比,后者要求用户专用化类模板。您可以在此处查看旧版本:

https://bitbucket.org/davidstone/bounded_integer/src/2defec41add2079ba023c2c6d118ed8a274423c8/bounded_integer/detail/optional/optional.hpp

https://bitbucket.org/davidstone/bounded_integer/src/2defec41add2079ba023c2c6d118ed8a274423c8/bounded_integer/detail/optional/specialization.hpp

这种方法的问题在于,它只是对用户来说更多的工作。用户必须进入新的命名空间并专用化模板,而不是添加四个成员函数。

实际上,所有专用化都有一个in_place_t构造函数,该构造函数将所有参数转发到基础类型。另一方面,optional_tag方法可以直接使用基础类型的构造函数。

在专用optional_storage方法中,用户还负责添加值函数的适当引用限定重载。在optional_tag方法中,我们已经有了价值,所以我们不必把它拉出来。

optional_storage还需要标准化作为可选两个帮助程序类接口的一部分,用户只应该专门化其中一个(有时将其专用化委托给另一个(。

这和compact_optional的区别

compact_optional是一种说法,"将这个特殊的哨兵值视为不存在的类型,几乎就像 NaN 一样"。它要求用户知道他们正在使用的类型具有一些特殊的哨兵。一个易于专业化的optional是一种说法:"我的类型不需要额外的空间来存储不存在的状态,但该状态不是正常值。它不需要任何人了解优化即可利用它;使用该类型的每个人都可以免费获得它。

未来

我的目标是首先将其放入boost::optional,然后是std::optional提案的一部分。在此之前,您始终可以使用 bounded::optional ,尽管它有一些其他(有意的(界面差异。

我不明白允许或不允许某些特定的位模式表示未参与状态如何属于标准涵盖的任何内容。

如果你试图说服库供应商这样做,它需要一个实现,详尽的测试来证明你没有无意中吹嘘任何可选(或意外调用未定义行为(的要求,以及广泛的基准测试,以证明这在现实世界(而不仅仅是人为的(情况下会产生显着差异。

当然,你可以对自己的代码做任何你想做的事情。