专攻std :: optional

时间:2013-09-29 17:35:56

标签: c++ std template-specialization c++14

是否可以为用户定义的类型专门化std::optional?如果没有,那么提出这个标准是否为时已晚?

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

我是否可以在namespace std

中创建此专精

5 个答案:

答案 0 :(得分:9)

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

  

只有当声明取决于用户定义的类型且专业化符合原始模板的标准库要求且未明确指定时,程序才可以将任何标准库模板的模板特化添加到命名空间std   禁止的。

所以我会说它是允许的。

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

答案 1 :(得分:5)

如果你是在一个有效地打包价值的图书馆之后,那就是" no-value"标记到一个内存位置,我建议查看compact_optional。它就是这样做的。

它没有专门化boost::optionalstd::experimental::optional,但它可以将它们包装在内部,为您提供统一的界面,在可能的情况下进行优化并回退到经典的'在需要的地方可选。

答案 2 :(得分:2)

关于将optional<bool>optional<tribool>与其他示例一起专门化,我只问过一个字节。虽然没有讨论做这些事情的“合法性”,但我认为理论上不应该允许专门化optional<T>而不是例如:hash(明确允许)。

我没有包含日志,但部分原因是接口将对数据的访问视为对指针或引用的访问,这意味着如果在内部使用不同的数据结构,则一些访问不变量可能会改变;更不用说提供接口访问数据可能需要reinterpret_cast<(some_reference_type)>之类的东西。例如,使用uint8_t存储optional-bool会对optional<bool>的{​​{1}}接口施加一些与optional<T>不同的额外要求。例如,operator*的返回类型应该是什么?

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

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

答案 3 :(得分:0)

我没有看到允许或不允许某些特定位模式表示未处理状态属于标准所涵盖的任何内容。

如果您试图说服图书馆供应商这样做,则需要一个实施,详尽的测试表明您没有无意中吹嘘任何可选(或意外调用的未定义行为)的要求和广泛的基准测试来显示这在现实世界(而不仅仅是人为的)情境中产生了显着的差异。

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

答案 4 :(得分:0)

轻松选择节省空间

我已经确定这是一件很有用的事情,但是完全专业化比必要的工作要多一些(例如,让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方法可以直接使用底层类型的构造函数。

在specialize optional_storage方法中,用户还有责任添加值函数的正确引用限定重载。在optional_tag方法中,我们已经拥有了价值,因此我们无需将其拉出来。

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

这与compact_optional

之间的区别

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

未来

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