std ::引用类型的可选专门化

Ori*_*ent 22 c++ stl optional c++11 boost-optional

为什么std::optional(目前std::experimental::optionallibc ++中)没有引用类型的专门化(与之相比boost::optional)?

我认为这将是非常有用的选择.

是否有一些对象引用STL中可能已存在的对象语义?

Nev*_*vin 16

讨论n3406(提案的第2版)时,一些委员会成员对可选参考文献感到不舒服.在n3527(修订版#3)中,作者决定将可选引用作为辅助提议,以增加获得可选值的机会并将其置于C++ 14中.虽然由于各种其他原因,可选项并未完全进入C++ 14,但委员会并未拒绝可选引用,并且如果有人提出,可以在将来自由添加可选引用.

  • @ShmuelLevine 我不确定会议上的担忧是什么,但一个巨大的担忧是: `int y = 2; 应该如何?整数x = 1; std::可选<int&> x_ref {x}; x_ref = y;`表现如何?“x_ref”现在应该是对“y”的引用还是“x”现在应该等于“2”? (5认同)
  • 我意识到这是一个非常古老的答案,但我想知道你是否能够对这些具体的不适提供一些澄清?(我猜更好 - 是)这仅仅是一个分配是否重新绑定以引用不同对象的问题(根据 boost 实现)?还有其他问题吗? (2认同)

Arn*_*rtz 7

确实有一些东西可能引用了现有的对象语义.它被称为(const)指针.一个普通的旧非拥有指针.引用和指针之间有三个不同之处:

  1. 指针可以为null,引用不能.这正是您想要避免的差异std::optional.
  2. 指针可以重定向到指向别的东西.使它成为常数,这种差异也会消失.
  3. 参考文献无需通过->或取消引用*.这是纯粹的语法糖,可能因为1.指针语法(解除引用和转换为bool)正是std::optional提供访问值并测试其存在的原因.

更新: optional是值的容器.与其他容器(vector例如)一样,它不是为了包含引用而设计的.如果你想要一个可选的引用,使用指针,或者如果你确实需要一个具有类似语法的接口,那么std::optional为指针创建一个小的(和普通的)包装器.

更新2:至于为什么没有这样的专业化的问题:因为委员会只是选择了它.理由可以在论文的某处找到.这可能是因为他们认为指针足够了.

  • 但是,对于泛型编程,即使`T == U&`,你也希望`std :: optional <T>`wo工作.你可以很好地解释`std :: optinal <U&>`是如何用`U*const`实现的,但这对通用编程来说并不是真正的帮助. (17认同)
  • @MSalters我不明白你为什么会这么想.`optional`严格来说是值.请参阅我对答案的更新."泛型编程"并不是一个可以想象的任何理由.如果您碰巧遇到过想要支持可选值*和*可选引用的场合,请使用"optional"和"指针"之间的distingusih或我提到的包装类. (4认同)
  • 按照今天的标准,我会说这个答案是错误的。如果我看到一个指针,我立即开始怀疑它是否拥有它所引用的对象,该对象是否是动态分配的(因此可能随时被删除)等等。我不必怀疑关于引用的问题也不是一个可选的。原因是指针在传统上涵盖了很多可能的用例,而引用和可选涵盖的方式要少得多。它们是为特定目的而设计的,当它们被使用时,我知道它们是用于这些目的的。因此,标准应该支持`std::optional&lt;T&amp;&gt;`。 (4认同)
  • @monkey0506 原始点**不能**代表所有权,永远。无论我们谈论的是 C 还是 C++。你总是不得不猜测,如果我们继续在遗留代码的上下文中这样做,那么它是中性的。 (4认同)
  • 按照今天的标准,原始指针永远不应该拥有被引用的对象。引用,可选的或其他的,也可以绑定到堆对象,所以在动态对象生命周期的危险方面没有任何区别。诚然,在老式 C++ 中,原始指针可能意味着从所有者到引用再到数组迭代器的任何内容。在今天的代码中,所有这些情况都应该以不同的方式处理。`std::optional&lt;T&amp;&gt;` 目前被大量讨论,语义并不太清楚 - AFAIK 关于分配是应该重新绑定还是分配引用的对象,大约有 50/50 的分裂。 (3认同)
  • 但这显然不是真的。您可能指的是 C API 中的某种约定(尽管我现在只能想到反例)。接口函数可以记录生命周期/所有权假设。当然,使用表达类型要容易得多,它不仅使意图清晰,而且还允许检查或自动生命周期保证。基本上,当您**必须**“与依赖于原始指针的代码进行互操作”时,根据定义,您依赖于在代码/类型之外定义的所有权语义。 (3认同)
  • 一生不是所有权。这就是我们沟通不畅的根源。生命周期和所有权是相关的,但却是截然不同的概念。我不欣赏你在本应是消除误解的正常尝试中表现出恶意的方式。 (3认同)
  • “按照今天的标准,原始指针永远不应该拥有所引用的对象。” 是一个极其狭隘和不切实际的POV。在完美的世界中,所有代码都将符合“当今的标准”,但在现实世界中,我们必须与旧的代码库进行互操作,因为我们不能每隔几年就重新发明轮子。特别是如果使用任何 C 库,原始指针**必须**代表所有权。 (2认同)
  • 我不是在谈论“语法所有权”(实际上没有这样的东西,请参阅[值类别](https://en.cppreference.com/w/cpp/language/value_category))。我正在谈论_所有权语义_。*您*正在谈论的是所有权约定/信念。如果它只是在你的脑海中,那么它就不是语义。如果它描述了 C++ 抽象机的行为,那么它就是语义。所以,你就是那个混淆概念的人。 (2认同)

Sas*_*sha 5

主要问题std::optional <T&>是 -optRef = obj在以下情况下应该做什么:

optional<T&> optRef;
…;
T obj {…};
optRef = obj; // <-- here!
Run Code Online (Sandbox Code Playgroud)

变体:

  1. 总是重新绑定 - (&optRef)->~optional(); new (&optRef) optional<T&>(obj)
  2. 分配通过 - *optRef = obj(UB!optRef之前)。
  3. 如果为空则绑定,否则通过分配 — if (optRef) {do1;} else {do2;}
  4. 无赋值运算符 — 编译时错误“试图使用已删除的运算符”。

每个变体的优点:

  1. 始终重新绑定(由boost::optionaln1878 选择):

    • !optRefoptRef.has_value()- 后置条件始终满足时,情况之间的一致性。&*optRef == &obj
    • 与通常optional<T>在以下方面的一致性:对于通常optional<T>,如果T::operator=定义为破坏和构造(并且有人认为它必须无非是对破坏和构造的优化),那么opt = … 事实上的行为类似于(&opt)->~optional(); new (&opt) optional<T&>(obj)
  2. 通过以下方式分配:

    • T&在以下方面与 pure 保持一致:对于 pure T&ref = …分配通过(不重新绑定ref)。
    • 稠度与通常optional<T>在以下方面:用于通常optional<T>,当opt.has_value()opt = …需要通过分配,不破坏和-构建体(见template <class U> optional<T>& optional<T>::operator=(U&& v)n3672上cppreference.com)。
    • optional<T>在以下方面与通常的一致性:两者都operator=至少以某种方式进行了定义。
  3. 如果为空则绑定,否则分配 - 我看不到真正的好处,恕我直言,只有当 #1 的支持者与 #2 的支持者争论时才会出现这种变体,无论在形式上它更符合要求的字母template <class U> optional<T>& optional<T>::operator=(U&& v)(但不符合精神,恕我直言)。

  4. 无赋值运算符(由n3406选择):

    • T&在以下方面与 pure 保持一致:pureT&不允许重新绑定自身。
    • 没有模棱两可的行为。

也可以看看:


Deu*_*hie 5

恕我直言,提供它是非常好的std::optional<T&>。然而,模板有一个微妙的问题。如果存在引用,模板参数的处理可能会变得棘手。

正如我们解决模板参数中引用问题的方法一样,我们可以使用 astd::reference_wrapper来避免std::optional<T&>. 那么现在就变成了std::optional<std::reference_wrapper<T>>。但是我建议不要使用这种方法,因为 1)它太冗长了,无法同时编写签名(尾随返回类型为我们节省了一点)和它的使用(我们必须调用std::reference_wrapper<T>::get()以获得真正的引用),以及 2)大多数程序员已经被指针折磨了,所以这就像一种本能反应,当他们收到一个指针时,他们首先测试它是否为空,所以现在这不是什么大问题。

  • `template &lt;typename T&gt; using optional_ref = std::optional&lt;std::reference_wrapper&lt;T&gt;&gt;` 部分解决了冗长的问题。 (2认同)
  • 原始指针的问题不仅仅是检查 nullptr:我应该获得函数返回的指针的所有权吗?我可以在我的对象中保存该指针吗?当使用Optional_ref时,您期望RAII并且可以应用我们习惯于遵循的所有那些操作方法。 (2认同)