为什么std::optional
(目前std::experimental::optional
在libc++中)没有针对引用类型的专门化(与 相比boost::optional
)?
我认为这将是非常有用的选择。
是否有一些对象引用了STL中可能已经存在的对象语义?
为什么std::optional
(目前std::experimental::optional
在libc++中)没有针对引用类型的专门化(与 相比boost::optional
)?
我认为这将是非常有用的选择。
是否有一些对象引用了STL中可能已经存在的对象语义?
确实有些东西可能引用了现有的对象语义。它被称为(常量)指针。一个普通的旧的非拥有指针。引用和指针之间存在三个区别:
std::optional
。->
or取消引用*
。这是纯粹的语法糖,因为 1 是可能的。指针语法(解引用和可转换为 bool)正是std::optional
提供访问值和测试其存在的方法。更新:
optional
是值的容器。与其他容器(vector
例如 )一样,它并非旨在包含引用。如果您想要一个可选引用,请使用指针,或者如果您确实需要一个与std::optional
.
更新2:至于为什么没有这种专业化的问题:因为委员会只是选择退出。理由可能在论文的某个地方找到。这可能是因为他们认为指针就足够了。
恕我直言,提供它是非常好的std::optional<T&>
。然而,关于模板有一个微妙的问题。如果有引用,模板参数可能会变得难以处理。
正如我们解决模板参数中引用问题的方式一样,我们可以使用 astd::reference_wrapper
来规避std::optional<T&>
. 所以现在变成了std::optional<std::reference_wrapper<T>>
. 但是我建议不要使用这种方法,因为 1)写签名(尾随返回类型为我们节省一点)和使用它(我们必须调用std::reference_wrapper<T>::get()
以获取真正的参考)都太冗长,以及 2)大多数程序员已经受到指针的折磨,因此就像一种本能的反应,当他们收到指针时,他们首先测试它是否为空,所以现在这不是一个很大的问题。
如果我冒险猜测,那将是因为 std::experimental::optional 规范中的这句话。(第 5.2 节,p1)
需要
optional
为引用类型或可能为 cv 限定类型in_place_t
或 格式错误的模板实例化的程序nullopt_t
。
我偶然发现了好几次,我最终决定实现我的不依赖于 boost 的解决方案。对于引用类型,它禁用赋值运算符并且不允许比较指针或 r 值。它基于我前一段时间所做的一项类似工作nullptr
,它使用而不是nullopt
表示没有价值。出于这个原因,该类型被调用nullable
,并且指针类型的编译被禁用(它们nullptr
无论如何都有)。如果您发现任何明显或不明显的问题,请告诉我。
#ifndef NULLABLE_H
#define NULLABLE_H
#pragma once
#include <cstddef>
#include <stdexcept>
#include <type_traits>
namespace usr {
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>& op1, const nullable<T2>& op2);
template <typename T2>
friend bool operator!=(const nullable<T2>& op1, const nullable<T2>& op2);
template <typename T2>
friend bool operator==(const nullable<T2>& op, const T2& value);
template <typename T2>
friend bool operator==(const T2& value, const nullable<T2>& op);
template <typename T2>
friend bool operator==(const nullable<T2>& op, std::nullptr_t);
template <typename T2>
friend bool operator!=(const nullable<T2>& op, const T2& value);
template <typename T2>
friend bool operator!=(const T2& value, const nullable<T2>& op);
template <typename T2>
friend bool operator==(std::nullptr_t, const nullable<T2>& op);
template <typename T2>
friend bool operator!=(const nullable<T2>& op, std::nullptr_t);
template <typename T2>
friend bool operator!=(std::nullptr_t, const nullable<T2>& op);
private:
static const T& compare(const T& val)
{
return val;
}
private:
bool m_hasValue;
T m_value;
};
// Template spacialization for templates
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:
// NOTE: We don't provide comparison against value since
// it would be ambiguous
template <typename T2>
friend bool operator==(const nullable<T2>& op1, const nullable<T2>& op2);
template <typename T2>
friend bool operator!=(const nullable<T2>& op1, const nullable<T2>& op2);
template <typename T2>
friend bool operator==(const nullable<T2>& op, std::nullptr_t);
template <typename T2>
friend bool operator==(std::nullptr_t, const nullable<T2>& op);
template <typename T2>
friend bool operator!=(const nullable<T2>& op, std::nullptr_t);
template <typename T2>
friend bool operator!=(std::nullptr_t, const nullable<T2>& op);
private:
bool m_hasValue;
T* m_value;
};
template <typename T2>
bool operator==(const nullable<T2>& n1, const nullable<T2>& n2)
{
if (n1.m_hasValue != n2.m_hasValue)
return false;
if (n1.m_hasValue)
return n1.m_value == n2.m_value;
else
return true;
}
template <typename T2>
bool operator!=(const nullable<T2>& op1, const nullable<T2>& op2)
{
if (op1.m_hasValue != op2.m_hasValue)
return true;
if (op1.m_hasValue)
return op1.m_value != op2.m_value;
else
return false;
}
template <typename T2>
bool operator==(const nullable<T2>& n, const T2& v)
{
if (!n.m_hasValue)
return false;
return n.m_value == v;
}
template <typename T2>
bool operator!=(const nullable<T2>& n, const T2& v)
{
if (!n.m_hasValue)
return true;
return n.m_value != v;
}
template <typename T2>
bool operator==(const T2& v, const nullable<T2>& n)
{
if (!n.m_hasValue)
return false;
return n.m_value == v;
}
template <typename T2>
bool operator!=(const T2& v, const nullable<T2>& n)
{
if (!n.m_hasValue)
return false;
return n.m_value != v;
}
template <typename T2>
bool operator==(const nullable<T2>& n, std::nullptr_t)
{
return !n.m_hasValue;
}
template <typename T2>
bool operator!=(const nullable<T2>& n, std::nullptr_t)
{
return n.m_hasValue;
}
template <typename T2>
bool operator==(std::nullptr_t, const nullable<T2>& n)
{
return !n.m_hasValue;
}
template <typename T2>
bool operator!=(std::nullptr_t, const nullable<T2>& n)
{
return n.m_hasValue;
}
} // namespace usr
#endif // NULLABLE_H