37

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

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

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

4

6 回答 6

23

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

于 2014-11-12T20:04:37.340 回答
13

确实有些东西可能引用了现有的对象语义。它被称为(常量)指针。一个普通的旧的非拥有指针。引用和指针之间存在三个区别:

  1. 指针可以为空,引用不能。这正是您想要规避的差异std::optional
  2. 指针可以被重定向以指向其他东西。将其设为 const,这种差异也会消失。
  3. 引用不需要由->or取消引用*。这是纯粹的语法糖,因为 1 是可能的。指针语法(解引用和可转换为 bool)正是std::optional提供访问值和测试其存在的方法。

更新: optional是值的容器。与其他容器(vector例如 )一样,它并非旨在包含引用。如果您想要一个可选引用,请使用指针,或者如果您确实需要一个与std::optional.

更新2:至于为什么没有这种专业化的问题:因为委员会只是选择退出。理由可能在论文的某个地方找到。这可能是因为他们认为指针就足够了。

于 2014-11-11T10:29:53.827 回答
8
于 2020-08-01T10:20:39.420 回答
3

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

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

于 2020-11-23T09:35:29.600 回答
0

如果我冒险猜测,那将是因为 std::experimental::optional 规范中的这句话。(第 5.2 节,p1)

需要optional 为引用类型或可能为 cv 限定类型in_place_t或 格式错误的模板实例化的程序nullopt_t

于 2014-11-11T06:26:47.353 回答
0

我偶然发现了好几次,我最终决定实现我的不依赖于 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
于 2021-10-16T17:12:47.843 回答