为什么std::optional(目前在**libc++**中的std::experimental::optional)没有引用类型的专门化(与boost::optional相比)?我认为这将是非常有用的选择。
std::optional
std::experimental::optional
boost::optional
STL中是否有某个对象 * 引用了可能已经存在的对象 * 语义?
yshpjwxd1#
当n3406(提案的修订版#2)进行了讨论,一些委员会成员对可选参考文献感到不安。(修订版#3),作者决定将可选引用作为一个辅助建议,以增加可选值获得批准并放入C14的机会。尽管由于各种其他原因,可选值没有完全进入C14,委员会没有拒绝任择性提及,今后如果有人提出,可自由添加任择性提及。
t98cgbkg2#
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赋值(当!optRef之前为UB时)。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=
另见:
kwvwclae3#
确实有一个东西是 reference tomaybeexisting object 语义的。它被称为(const)指针。一个普通的旧的非拥有指针。reference和指针之间有三个区别:1.指针可以为空,引用不能,这正是std::optional需要避免的区别。1.指针可以被重定向指向其他东西。使它成为常量,这种差异也会消失。1.引用不需要被->或*解引用。这是纯粹的语法糖,因为1而成为可能。指针语法(解引用并可转换为bool)正是std::optional为访问值和测试其存在而提供的。
->
*
更新:optional是一个值的容器。像其他容器一样(例如vector),它不是 * 设计 * 来包含引用的。如果你想要一个可选的引用,使用一个指针,或者如果你确实需要一个语法类似于std::optional的接口,为指针创建一个小的(和平凡的) Package 器。**更新2:**关于 * 为什么 * 没有这种专门化的问题:因为委员会确实选择了退出。理由 * 可能 * 在文件的某个地方找到。可能是因为他们认为指针就足够了。
optional
vector
qf9go6mv4#
恕我直言,使std::optional<T&>可用是非常好的。但是,关于模板有一个微妙的问题。如果有引用,模板参数可能会变得棘手。正如我们解决模板参数中引用问题的方法一样,我们可以使用std::reference_wrapper来避免缺少std::optional<T&>。因此,现在它变成了std::optional<std::reference_wrapper<T>>。但是,我建议不要使用这种用法,因为1)同时写入签名太冗长(尾随的返回类型节省了我们一点时间)以及它的使用(我们必须调用std::reference_wrapper<T>::get()以获得真实的引用),和2)大多数程序员已经被指针折磨过了,所以当他们接收到指针时,他们首先测试它是否为空,所以它是空的,这就像是一种本能React现在已经不是什么大问题了。
std::optional<T&>
std::reference_wrapper
std::optional<std::reference_wrapper<T>>
std::reference_wrapper<T>::get()
ef1yzkbh5#
如果我大胆猜测,这是因为std::experimental::optional规范中的这句话(第5.2节,p1)。对于引用类型,或者对于cv限定的类型in_place_t或nullopt_t,需要示例化模板optional的程序是病态的。
in_place_t
nullopt_t
r1zhe5dt6#
我偶然发现了这个问题好几次,最后我决定实现不依赖于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; // NOTE: We dont't do rebinding from other references nullable& operator=(const nullable& value) = delete; 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条答案
按热度按时间yshpjwxd1#
当n3406(提案的修订版#2)进行了讨论,一些委员会成员对可选参考文献感到不安。(修订版#3),作者决定将可选引用作为一个辅助建议,以增加可选值获得批准并放入C14的机会。尽管由于各种其他原因,可选值没有完全进入C14,委员会没有拒绝任择性提及,今后如果有人提出,可自由添加任择性提及。
t98cgbkg2#
std::optional <T&>
的主要问题是-optRef = obj
在以下情况下应该做什么:变体:
1.始终重新绑定-
(&optRef)->~optional(); new (&optRef) optional<T&>(obj)
。1.通过-
*optRef = obj
赋值(当!optRef
之前为UB时)。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&
在以下方面的一致性:pureT&
不允许重新绑定自身。另见:
kwvwclae3#
确实有一个东西是 reference tomaybeexisting object 语义的。它被称为(const)指针。一个普通的旧的非拥有指针。reference和指针之间有三个区别:
1.指针可以为空,引用不能,这正是
std::optional
需要避免的区别。1.指针可以被重定向指向其他东西。使它成为常量,这种差异也会消失。
1.引用不需要被
->
或*
解引用。这是纯粹的语法糖,因为1而成为可能。指针语法(解引用并可转换为bool)正是std::optional
为访问值和测试其存在而提供的。更新:
optional
是一个值的容器。像其他容器一样(例如vector
),它不是 * 设计 * 来包含引用的。如果你想要一个可选的引用,使用一个指针,或者如果你确实需要一个语法类似于std::optional
的接口,为指针创建一个小的(和平凡的) Package 器。**更新2:**关于 * 为什么 * 没有这种专门化的问题:因为委员会确实选择了退出。理由 * 可能 * 在文件的某个地方找到。可能是因为他们认为指针就足够了。
qf9go6mv4#
恕我直言,使
std::optional<T&>
可用是非常好的。但是,关于模板有一个微妙的问题。如果有引用,模板参数可能会变得棘手。正如我们解决模板参数中引用问题的方法一样,我们可以使用
std::reference_wrapper
来避免缺少std::optional<T&>
。因此,现在它变成了std::optional<std::reference_wrapper<T>>
。但是,我建议不要使用这种用法,因为1)同时写入签名太冗长(尾随的返回类型节省了我们一点时间)以及它的使用(我们必须调用std::reference_wrapper<T>::get()
以获得真实的引用),和2)大多数程序员已经被指针折磨过了,所以当他们接收到指针时,他们首先测试它是否为空,所以它是空的,这就像是一种本能React现在已经不是什么大问题了。ef1yzkbh5#
如果我大胆猜测,这是因为std::experimental::optional规范中的这句话(第5.2节,p1)。
对于引用类型,或者对于cv限定的类型
in_place_t
或nullopt_t
,需要示例化模板optional
的程序是病态的。r1zhe5dt6#
我偶然发现了这个问题好几次,最后我决定实现不依赖于boost的解决方案。对于引用类型,它禁用赋值运算符,不允许比较指针或r值。它基于我以前做的类似work,使用
nullptr
而不是nullopt
来表示缺少值。因此,这个类型叫做nullable
,并且指针类型的编译是禁用的(不管怎样,它们都有nullptr
)。如果你发现任何明显或不明显的问题,请告诉我。