c++ std::引用类型的可选专用化

mznpcxlj  于 2022-12-27  发布在  其他
关注(0)|答案(6)|浏览(138)

为什么std::optional(目前在**libc++**中的std::experimental::optional)没有引用类型的专门化(与boost::optional相比)?
我认为这将是非常有用的选择。

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

yshpjwxd

yshpjwxd1#

当n3406(提案的修订版#2)进行了讨论,一些委员会成员对可选参考文献感到不安。(修订版#3),作者决定将可选引用作为一个辅助建议,以增加可选值获得批准并放入C14的机会。尽管由于各种其他原因,可选值没有完全进入C14,委员会没有拒绝任择性提及,今后如果有人提出,可自由添加任择性提及。

t98cgbkg

t98cgbkg2#

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.没有赋值运算符-尝试使用已删除的运算符""时出现编译时错误""。
每种变体的优点:

  • 始终重新绑定(由boost::optionaln1878选择):
  • 始终满足!optRefoptRef.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=
  • 如果为空,则绑定,否则分配--我看不出有什么真正的好处,恕我直言,只有当#1的支持者与#2的支持者发生争论时,才会出现这种变体,无论它在形式上与template <class U> optional<T>& optional<T>::operator=(U&& v)的要求的文字(但与精神不一致,恕我直言)更加一致。
  • 无赋值运算符(由n3406选择):
  • 与纯T&在以下方面的一致性:pure T&不允许重新绑定自身。
  • 没有模棱两可的行为。

另见:

kwvwclae

kwvwclae3#

确实有一个东西是 reference tomaybeexisting object 语义的。它被称为(const)指针。一个普通的旧的非拥有指针。reference和指针之间有三个区别:
1.指针可以为空,引用不能,这正是std::optional需要避免的区别。
1.指针可以被重定向指向其他东西。使它成为常量,这种差异也会消失。
1.引用不需要被->*解引用。这是纯粹的语法糖,因为1而成为可能。指针语法(解引用并可转换为bool)正是std::optional为访问值和测试其存在而提供的。

更新:optional是一个值的容器。像其他容器一样(例如vector),它不是 * 设计 * 来包含引用的。如果你想要一个可选的引用,使用一个指针,或者如果你确实需要一个语法类似于std::optional的接口,为指针创建一个小的(和平凡的) Package 器。
**更新2:**关于 * 为什么 * 没有这种专门化的问题:因为委员会确实选择了退出。理由 * 可能 * 在文件的某个地方找到。可能是因为他们认为指针就足够了。

qf9go6mv

qf9go6mv4#

恕我直言,使std::optional<T&>可用是非常好的。但是,关于模板有一个微妙的问题。如果有引用,模板参数可能会变得棘手。
正如我们解决模板参数中引用问题的方法一样,我们可以使用std::reference_wrapper来避免缺少std::optional<T&>。因此,现在它变成了std::optional<std::reference_wrapper<T>>。但是,我建议不要使用这种用法,因为1)同时写入签名太冗长(尾随的返回类型节省了我们一点时间)以及它的使用(我们必须调用std::reference_wrapper<T>::get()以获得真实的引用),和2)大多数程序员已经被指针折磨过了,所以当他们接收到指针时,他们首先测试它是否为空,所以它是空的,这就像是一种本能React现在已经不是什么大问题了。

ef1yzkbh

ef1yzkbh5#

如果我大胆猜测,这是因为std::experimental::optional规范中的这句话(第5.2节,p1)。
对于引用类型,或者对于cv限定的类型in_place_tnullopt_t,需要示例化模板optional的程序是病态的。

r1zhe5dt

r1zhe5dt6#

我偶然发现了这个问题好几次,最后我决定实现不依赖于boost的解决方案。对于引用类型,它禁用赋值运算符,不允许比较指针或r值。它基于我以前做的类似work,使用nullptr而不是nullopt来表示缺少值。因此,这个类型叫做nullable,并且指针类型的编译是禁用的(不管怎样,它们都有nullptr)。如果你发现任何明显或不明显的问题,请告诉我。

#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

相关问题