为什么std::optional(std::experimental::optional目前在libc++中)没有对引用类型的专门化(与boost::optional相比)?我认为这将是非常有用的选择。在标准库中是否有一些对象 * 引用了可能已经存在的对象 * 语义?
std::optional
std::experimental::optional
boost::optional
1qczuiv01#
当n3406(提案的修订版#2)进行了讨论,一些委员会成员对可选引用感到不安。(修订版#3),作者决定将可选引用作为一个辅助建议,以增加可选值获得批准并放入C14的机会。虽然由于各种其他原因,可选值并没有完全进入C14,委员会并不拒绝任择提及,今后如果有人提出,委员会可自由增加任择提及。
ru9i0ody2#
std::optional <T&>的主要问题是-optRef = obj在以下情况下应该怎么做:
std::optional <T&>
optRef = obj
optional<T&> optRef; …; T obj {…}; optRef = obj; // <-- here!
字符串变体:1.始终重新绑定-(&optRef)->~optional(); new (&optRef) optional<T&>(obj)。1.通过-*optRef = obj赋值(UB在!optRef之前时)。1.如果为空则绑定,否则通过-if (optRef) {do1;} else {do2;}赋值。1.没有赋值运算符-编译时错误“尝试使用已删除的运算符”。各款产品的优点:
(&optRef)->~optional(); new (&optRef) optional<T&>(obj)
*optRef = obj
!optRef
if (optRef) {do1;} else {do2;}
optRef.has_value()
**&***optRef == **&**obj
optional<T>
T::operator=
opt = …
(&opt)->~optional(); new (&opt) optional<T&>(obj)
T&
ref = …
ref
opt.has_value()
template <class U> optional<T>& optional<T>::operator=(U&& v)
operator=
另请参阅:
8yparm6h3#
确实有一些东西具有 reference tomaybeexisting object 的语义。它被称为(const)pointer。一个普通的旧的无所有权指针。reference和pointer之间有三个区别:1.指针可以为空,引用不能。这正是std::optional要避免的区别。1.指针可以被重定向指向其他东西。让它成为常量,这种差异也就消失了。1.引用不需要被->或*解引用。这是纯粹的语法糖,因为1是可能的。指针语法(解引用并可转换为bool)正是std::optional提供的访问值和测试其存在的语法。
->
*
更新:optional是一个值容器。像其他容器(例如vector)一样,它不是 * 设计 * 来包含引用的。如果你想要一个可选的引用,使用指针,或者如果你确实需要一个与std::optional语法相似的接口,为指针创建一个小的(和普通的) Package 器。**更新2:**至于问题 * 为什么 * 没有这样的专业化:因为委员会只是选择了它。理由 * 可能 * 在文件中找到。这可能是因为他们认为指针就足够了。
optional
vector
gojuced74#
恕我直言,让std::optional<T&>可用是非常好的。然而,关于模板有一个微妙的问题。如果有引用,模板参数可能会变得棘手。正如我们解决模板参数中引用问题的方法一样,我们可以使用std::reference_wrapper来规避std::optional<T&>的缺失。所以现在它变成了std::optional<std::reference_wrapper<T>>。但是我建议不要使用这种方法,因为1)它太冗长了,既要写签名,(尾随返回类型为我们节省了一点)和它的使用(我们必须调用std::reference_wrapper<T>::get()来获取真实的引用),(二)大多数程序员已经被指针折磨过了,所以当他们收到一个指针时,他们就像本能的React一样,首先测试它是否为空。现在不是什么大问题了
std::optional<T&>
std::reference_wrapper
std::optional<std::reference_wrapper<T>>
std::reference_wrapper<T>::get()
q1qsirdb5#
如果让我大胆猜测的话,那是因为std::experimental::optional规范中的这句话。(第5.2节,p1)如果一个程序需要示例化引用类型的模板optional,或者可能是cv限定的类型in_place_t或nullopt_t,那么这个程序就是病态的。
in_place_t
nullopt_t
nbnkbykc6#
我偶然发现了这个问题好几次,最后我决定实现我的解决方案,不依赖于boost。对于引用类型,它禁用赋值运算符,不允许比较指针或r值。它基于一个类似的work,我以前做过,它使用nullptr而不是nullopt来表示值的缺失。出于这个原因,该类型被称为nullable和编译是禁用指针类型(他们有nullptr反正).请让我知道,如果你发现任何明显或任何不明显的问题与它.
nullptr
nullopt
nullable
#ifndef COMMON_NULLABLE_H #define COMMON_NULLABLE_H #pragma once #include <cstddef> #include <stdexcept> #include <type_traits> namespace COMMON_NAMESPACE { class bad_nullable_access : public std::runtime_error { public: bad_nullable_access() : std::runtime_error("nullable object doesn't have a value") { } }; /** * Alternative to std::optional that supports reference (but not pointer) types */ template <typename T, typename = std::enable_if_t<!std::is_pointer<T>::value>> class nullable final { public: nullable() : m_hasValue(false), m_value{ } { } nullable(T value) : m_hasValue(true), m_value(std::move(value)) { } nullable(std::nullptr_t) : m_hasValue(false), m_value{ } { } nullable(const nullable& value) = default; nullable& operator=(const nullable& value) = default; nullable& operator=(T value) { m_hasValue = true; m_value = std::move(value); return *this; } nullable& operator=(std::nullptr_t) { m_hasValue = false; m_value = { }; return *this; } const T& value() const { if (!m_hasValue) throw bad_nullable_access(); return m_value; } T& value() { if (!m_hasValue) throw bad_nullable_access(); return m_value; } bool has_value() const { return m_hasValue; } const T* operator->() const { return &m_value; } T* operator->() { return &m_value; } const T& operator*() const { return m_value; } T& operator*() { return m_value; } public: template <typename T2> friend bool operator==(const nullable<T2>& lhs, const nullable<T2>& rhs); template <typename T2> friend bool operator!=(const nullable<T2>& lhs, const nullable<T2>& rhs); template <typename T2> friend bool operator==(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs); template <typename T2> friend bool operator!=(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs); template <typename T2> friend bool operator==(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs); template <typename T2> friend bool operator!=(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs); template <typename T2> friend bool operator==(const nullable<T2>& lhs, const T2& rhs); template <typename T2> friend bool operator==(const T2& lhs, const nullable<T2>& rhs); template <typename T2> friend bool operator==(const nullable<T2>& lhs, std::nullptr_t); template <typename T2> friend bool operator!=(const nullable<T2>& lhs, const T2& rhs); template <typename T2> friend bool operator!=(const T2& lhs, const nullable<T2>& rhs); template <typename T2> friend bool operator==(std::nullptr_t, const nullable<T2>& rhs); template <typename T2> friend bool operator!=(const nullable<T2>& lhs, std::nullptr_t); template <typename T2> friend bool operator!=(std::nullptr_t, const nullable<T2>& rhs); private: bool m_hasValue; T m_value; }; // Template spacialization for references template <typename T> class nullable<T&> final { public: nullable() : m_hasValue(false), m_value{ } { } nullable(T& value) : m_hasValue(true), m_value(&value) { } nullable(std::nullptr_t) : m_hasValue(false), m_value{ } { } nullable(const nullable& value) = default; nullable& operator=(const nullable& value) = default; const T& value() const { if (!m_hasValue) throw bad_nullable_access(); return *m_value; } T& value() { if (!m_hasValue) throw bad_nullable_access(); return *m_value; } bool has_value() const { return m_hasValue; } const T* operator->() const { return m_value; } T* operator->() { return m_value; } const T& operator*() const { return *m_value; } T& operator*() { return *m_value; } public: template <typename T2> friend bool operator==(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs); template <typename T2> friend bool operator!=(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs); template <typename T2> friend bool operator==(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs); template <typename T2> friend bool operator!=(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs); template <typename T2> friend bool operator==(const nullable<T2&>& lhs, const nullable<T2&>& rhs); template <typename T2> friend bool operator!=(const nullable<T2&>& lhs, const nullable<T2&>& rhs); template <typename T2> friend bool operator==(const nullable<T2&>& lhs, const std::decay_t<T2>& rhs); template <typename T2> friend bool operator!=(const nullable<T2&>& lhs, const std::decay_t<T2>& rhs); template <typename T2> friend bool operator==(const std::decay_t<T2>& lhs, const nullable<T2&>& rhs); template <typename T2> friend bool operator!=(const std::decay_t<T2>& lhs, const nullable<T2&>& rhs); template <typename T2> friend bool operator==(const nullable<T2>& lhs, std::nullptr_t); template <typename T2> friend bool operator==(std::nullptr_t, const nullable<T2>& rhs); template <typename T2> friend bool operator!=(const nullable<T2>& lhs, std::nullptr_t); template <typename T2> friend bool operator!=(std::nullptr_t, const nullable<T2>& rhs); private: bool m_hasValue; T* m_value; }; template <typename T> using nullableref = nullable<T&>; template <typename T2> bool operator==(const nullable<T2>& lhs, const nullable<T2>& rhs) { if (lhs.m_hasValue != rhs.m_hasValue) return false; if (lhs.m_hasValue) return lhs.m_value == rhs.m_value; else return true; } template <typename T2> bool operator!=(const nullable<T2>& lhs, const nullable<T2>& rhs) { if (lhs.m_hasValue != rhs.m_hasValue) return true; if (lhs.m_hasValue) return lhs.m_value != rhs.m_value; else return false; } template <typename T2> bool operator==(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs) { if (lhs.m_hasValue != rhs.m_hasValue) return true; if (lhs.m_hasValue) return lhs.m_value != *rhs.m_value; else return false; } template <typename T2> bool operator!=(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs) { if (lhs.m_hasValue != rhs.m_hasValue) return true; if (lhs.m_hasValue) return lhs.m_value != *rhs.m_value; else return false; } template <typename T2> bool operator==(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs) { if (lhs.m_hasValue != rhs.m_hasValue) return false; if (lhs.m_hasValue) return *lhs.m_value == rhs.m_value; else return true; } template <typename T2> bool operator!=(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs) { if (lhs.m_hasValue != rhs.m_hasValue) return true; if (lhs.m_hasValue) return *lhs.m_value != rhs.m_value; else return false; } template <typename T2> bool operator==(const nullable<T2&>& lhs, const nullable<T2&>& rhs) { if (lhs.m_hasValue != rhs.m_hasValue) return false; if (lhs.m_hasValue) return *lhs.m_value == *rhs.m_value; else return true; } template <typename T2> bool operator!=(const nullable<T2&>& lhs, const nullable<T2&>& rhs) { if (lhs.m_hasValue != rhs.m_hasValue) return true; if (lhs.m_hasValue) return *lhs.m_value != *rhs.m_value; else return false; } template <typename T2> bool operator==(const nullable<T2&>& lhs, const std::decay_t<T2>& rhs) { if (!lhs.m_hasValue) return false; return *lhs.m_value == rhs; } template <typename T2> bool operator!=(const nullable<T2&>& lhs, const std::decay_t<T2>& rhs) { if (!lhs.m_hasValue) return true; return *lhs.m_value != rhs; } template <typename T2> bool operator==(const std::decay_t<T2>& lhs, const nullable<T2&>& rhs) { if (!rhs.m_hasValue) return false; return lhs == *rhs.m_value; } template <typename T2> bool operator!=(const std::decay_t<T2>& lhs, const nullable<T2&>& rhs) { if (!rhs.m_hasValue) return true; return lhs != *rhs.m_value; } template <typename T2> bool operator==(const nullable<T2>& lhs, const T2& rhs) { if (!lhs.m_hasValue) return false; return lhs.m_value == rhs; } template <typename T2> bool operator!=(const nullable<T2>& lhs, const T2& rhs) { if (!lhs.m_hasValue) return true; return lhs.m_value != rhs; } template <typename T2> bool operator==(const T2& lhs, const nullable<T2>& rhs) { if (!rhs.m_hasValue) return false; return lhs == rhs.m_value; } template <typename T2> bool operator!=(const T2& lhs, const nullable<T2>& rhs) { if (!rhs.m_hasValue) return true; return lhs != rhs.m_value; } template <typename T2> bool operator==(const nullable<T2>& lhs, std::nullptr_t) { return !lhs.m_hasValue; } template <typename T2> bool operator!=(const nullable<T2>& lhs, std::nullptr_t) { return lhs.m_hasValue; } template <typename T2> bool operator==(std::nullptr_t, const nullable<T2>& rhs) { return !rhs.m_hasValue; } template <typename T2> bool operator!=(std::nullptr_t, const nullable<T2>& rhs) { return rhs.m_hasValue; } } #endif // COMMON_NULLABLE_H
字符串
6条答案
按热度按时间1qczuiv01#
当n3406(提案的修订版#2)进行了讨论,一些委员会成员对可选引用感到不安。(修订版#3),作者决定将可选引用作为一个辅助建议,以增加可选值获得批准并放入C14的机会。虽然由于各种其他原因,可选值并没有完全进入C14,委员会并不拒绝任择提及,今后如果有人提出,委员会可自由增加任择提及。
ru9i0ody2#
std::optional <T&>
的主要问题是-optRef = obj
在以下情况下应该怎么做:字符串
变体:
1.始终重新绑定-
(&optRef)->~optional(); new (&optRef) optional<T&>(obj)
。1.通过-
*optRef = obj
赋值(UB在!optRef
之前时)。1.如果为空则绑定,否则通过-
if (optRef) {do1;} else {do2;}
赋值。1.没有赋值运算符-编译时错误“尝试使用已删除的运算符”。
各款产品的优点:
!optRef
和optRef.has_value()
-后置条件**&***optRef == **&**obj
时的情况之间的一致性。optional<T>
在以下方面的一致性:对于通常的optional<T>
,如果T::operator=
被定义为破坏和构造(一些人认为它 * 必须 * 只不过是破坏和构造的优化),opt = …
* 事实上 * 的行为类似于(&opt)->~optional(); new (&opt) optional<T&>(obj)
。T&
在以下方面保持一致:对于纯T&
,ref = …
通过赋值(而不是重新绑定ref
)。optional<T>
在以下方面保持一致:对于通常的optional<T>
,当opt.has_value()
时,需要通过opt = …
进行赋值,而不是破坏并构造(参见n3672中的template <class U> optional<T>& optional<T>::operator=(U&& v)
和on cppreference.com)。optional<T>
在以下方面保持一致:两者都至少以某种方式定义了operator=
。template <class U> optional<T>& optional<T>::operator=(U&& v)
的要求更一致(但不符合其精神,恕我直言)。T&
在以下方面保持一致:纯T&
不允许重新绑定自身。另请参阅:
8yparm6h3#
确实有一些东西具有 reference tomaybeexisting object 的语义。它被称为(const)pointer。一个普通的旧的无所有权指针。reference和pointer之间有三个区别:
1.指针可以为空,引用不能。这正是
std::optional
要避免的区别。1.指针可以被重定向指向其他东西。让它成为常量,这种差异也就消失了。
1.引用不需要被
->
或*
解引用。这是纯粹的语法糖,因为1是可能的。指针语法(解引用并可转换为bool)正是std::optional
提供的访问值和测试其存在的语法。更新:
optional
是一个值容器。像其他容器(例如vector
)一样,它不是 * 设计 * 来包含引用的。如果你想要一个可选的引用,使用指针,或者如果你确实需要一个与std::optional
语法相似的接口,为指针创建一个小的(和普通的) Package 器。**更新2:**至于问题 * 为什么 * 没有这样的专业化:因为委员会只是选择了它。理由 * 可能 * 在文件中找到。这可能是因为他们认为指针就足够了。
gojuced74#
恕我直言,让
std::optional<T&>
可用是非常好的。然而,关于模板有一个微妙的问题。如果有引用,模板参数可能会变得棘手。正如我们解决模板参数中引用问题的方法一样,我们可以使用
std::reference_wrapper
来规避std::optional<T&>
的缺失。所以现在它变成了std::optional<std::reference_wrapper<T>>
。但是我建议不要使用这种方法,因为1)它太冗长了,既要写签名,(尾随返回类型为我们节省了一点)和它的使用(我们必须调用std::reference_wrapper<T>::get()
来获取真实的引用),(二)大多数程序员已经被指针折磨过了,所以当他们收到一个指针时,他们就像本能的React一样,首先测试它是否为空。现在不是什么大问题了q1qsirdb5#
如果让我大胆猜测的话,那是因为std::experimental::optional规范中的这句话。(第5.2节,p1)
如果一个程序需要示例化引用类型的模板
optional
,或者可能是cv限定的类型in_place_t
或nullopt_t
,那么这个程序就是病态的。nbnkbykc6#
我偶然发现了这个问题好几次,最后我决定实现我的解决方案,不依赖于boost。对于引用类型,它禁用赋值运算符,不允许比较指针或r值。它基于一个类似的work,我以前做过,它使用
nullptr
而不是nullopt
来表示值的缺失。出于这个原因,该类型被称为nullable
和编译是禁用指针类型(他们有nullptr
反正).请让我知道,如果你发现任何明显或任何不明显的问题与它.字符串