std:: дополнительная специализация для ссылочных типов

Зачем std::optional (std::experimental::optional в libC++ на данный момент) не имеет специализации для ссылочных типов (по сравнению с boost::optional)?

Я думаю, что это был бы очень полезный вариант.

Есть ли какой-либо объект со ссылкой на, возможно, уже существующую семантику объектов в STL?

6 ответов

Решение

Когда обсуждался вопрос346 (редакция № 2 предложения), некоторым членам комитета было неудобно с дополнительными ссылками. В n3527 (редакция № 3) авторы решили сделать необязательные ссылки вспомогательным предложением, чтобы повысить шансы получения дополнительных значений, утвержденных и помещенных в то, что стало C++14. Хотя необязательный не совсем вошел в C++ 14 по ряду других причин, комитет не отклонил необязательные ссылки и может добавлять необязательные ссылки в будущем, если кто-то предложит это.

Основная проблема с std::optional <T&> это - то, что должно optRef = obj сделать в следующем случае:

optional<T&> optRef;
…;
T obj {…};
optRef = obj; // <-- here!

Варианты:

  1. Всегда переплетать - (&optRef)->~optional(); new (&optRef) optional<T&>(obj).
  2. Назначить через - *optRef = obj (UB когда !optRef перед).
  3. Привязать, если пусто, в противном случае присвоить - if (optRef) {do1;} else {do2;}.
  4. Нет оператора присваивания - ошибка времени компиляции "попытка использовать удаленный оператор".

Плюсы каждого варианта:

  1. Всегда выполнять повторную привязку (выбирается с помощью boost:: optional и n1878):

    • Согласованность между случаями, когда !optRef а также optRef.has_value() - постусловие &*optRef == &obj всегда встречается.
    • Соответствие обычному optional<T> в следующем аспекте: для обычного optional<T>, если T::operator=определяется как разрушающее и созидающее (и некоторые утверждают, что это должно быть не что иное, как оптимизация для разрушения и построения),opt = … де-факто действует аналогично(&opt)->~optional(); new (&opt) optional<T&>(obj).
  2. Назначить через:

    • Последовательность с чистым T& в следующем аспекте: для чистого T&, ref = … назначает через (не повторно связывает ref).
    • Соответствие обычному optional<T> в следующем аспекте: для обычного optional<T>, когда opt.has_value(), opt = … требуется для присвоения, а не для разрушения и построения (см. template <class U> optional<T>& optional<T>::operator=(U&& v)в n3672 и на cppreference.com).
    • Соответствие обычному optional<T> в следующем аспекте: оба имеютoperator= определил хоть как-то.
  3. Привязать, если пусто, назначить через иначе - я не вижу реальных преимуществ, ИМХО, этот вариант возникает только тогда, когда сторонники №1 спорят со сторонниками №2, однако формально он даже больше соответствует букве требований для template <class U> optional<T>& optional<T>::operator=(U&& v) (но не с духом, ИМХО).

  4. Нет оператора присваивания (выбран n3406):

    • Последовательность с чистым T& в следующем аспекте: чистый T& не позволяет перепрограммировать себя.
    • Никакого двусмысленного поведения.

Смотрите также:

На самом деле есть нечто, имеющее ссылку на возможно существующую семантику объекта. Это называется (const) указатель. Простой старый не владеющий указатель. Есть три различия между ссылками и указателями:

  1. Указатели могут быть нулевыми, ссылки не могут. Это именно та разница, с которой вы хотите обойти std::optional,
  2. Указатели могут быть перенаправлены, чтобы указывать на что-то еще. Сделайте это постоянным, и эта разница исчезнет.
  3. Ссылки не должны быть разыменованы -> или же *, Это чистый синтаксический сахар и возможный из-за 1. И синтаксис указателя (разыменование и преобразование в bool) - это именно то, что std::optional обеспечивает доступ к значению и проверку его наличия.

Обновить: optional это контейнер для значений. Как и другие контейнеры (vector Например, он не предназначен для ссылок. Если вам нужна дополнительная ссылка, используйте указатель или вам действительно нужен интерфейс с похожим синтаксисом для std::optionalсоздайте небольшую (и тривиальную) оболочку для указателей.

Обновление 2: Что касается вопроса, почему такой специализации нет: потому что комитет просто отказался от нее. Обоснование может быть найдено где-то в газетах. Возможно, это потому, что они считают указатели достаточными.

ИМХО это нормально сделать std::optional<T&>имеется в наличии. Однако есть тонкая проблема с шаблонами. С параметрами шаблона может быть сложно справиться, если есть ссылки.

Так же, как мы решили проблему ссылок в параметрах шаблона, мы можем использовать std::reference_wrapper обойти отсутствие std::optional<T&>. Итак, теперь это становится std::optional<std::reference_wrapper<T>>. Однако я не рекомендую это использовать, потому что 1) это слишком многословно, чтобы писать подпись (конечный возвращаемый тип немного нас спасает) и ее использование (мы должны вызвать std::reference_wrapper<T>::get() чтобы получить реальную ссылку) и 2) большинство программистов уже были замучены указателями, так что это похоже на инстинктивную реакцию: когда они получают указатель, они сначала проверяют, является ли он нулевым, поэтому сейчас это не такая большая проблема.

Если бы я рискнул предположить, это было бы из-за этого предложения в спецификации std:: эксперимент:: необязательно. (Раздел 5.2, р1)

Программа, которая требует реализации шаблона optional для ссылочного типа или для, возможно, cv-квалифицированных типов in_place_t или же nullopt_t плохо сформирован.

Я наткнулся на это несколько раз и, наконец, решил реализовать свое решение, не зависящее от ускорения. Для ссылочных типов он отключает оператор присваивания и не позволяет сравнивать указатели или r-значения. Он основан на аналогичной работе, которую я проделал некоторое время назад, и вместо 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
Другие вопросы по тегам