Как я могу перегрузить оператор индекса, чтобы вернуть необязательный параметр, который может быть lvalue? - PullRequest
3 голосов
/ 14 июня 2019

Я изучаю некоторые функции C ++, реализуя класс octree . Я хочу, чтобы оператор индекса в этом классе возвращал октант , соответствующий индексу . Как определить оператор индекса в классе, чтобы я мог (i) назначить результат и (ii) проверить, является ли результат пустым или нет?

Цель (i) идиоматически достигается за счет того, что индексный оператор возвращает ссылку. Но ссылки не могут ссылаться ни на что, что является допустимым возвращаемым значением, если октант пуст.

Цель (ii) может быть достигнута, если заставить индексный оператор возвращать необязательный параметр. Но тогда становится нетривиальным изменять указатель на октант - с std::optional<T>, T не может быть ссылкой.

Вот неработающий пример (за исключением того, что все экземпляры optional имеют префикс experimental в реальном коде, потому что мой GCC имеет только экспериментальную поддержку C ++ 17).

#include <optional>
#include <iostream>

using namespace std;

class Octree {
    Octree* branch[8];
public:
    Octree();
    ~Octree();
    optional<Octree&> operator[](int index);
};

Octree::Octree() : branch{}
{
}

Octree::~Octree()
{
    for (int i = 0; i < 8; i++) {
        if (branch[i])
            delete branch[i];
    }
}

optional<Octree&> Octree::operator[](int index)
{
    if (branch[index] == NULL)
        return nullopt;
    else
        return &branch[index];
}

int main(int argc, char *argv[])
{
    Octree o;
    if (o[0])
        cout << "Octant o[0] is not empty.\n";
    else
        cout << "Octant o[0] is empty.\n";
    o[0] = new Octree(); // The intent is to modify o
    return 0;
}

Компилятор, как и ожидалось, красочно отклоняет необязательную ссылку.

In file included from parc.cpp:1:0:
/usr/include/c++/6/experimental/optional: In instantiation of ‘class std::experimental::fundamentals_v1::optional<Octree&>’:
parc.cpp:26:61:   required from here
/usr/include/c++/6/experimental/optional:507:7: error: static assertion failed: Invalid instantiation of optional<T>
       static_assert(__and_<__not_<is_same<remove_cv_t<_Tp>, nullopt_t>>,
       ^~~~~~~~~~~~~
/usr/include/c++/6/experimental/optional:713:7: error: forming pointer to reference type ‘Octree&’
       operator->() const
       ^~~~~~~~
/usr/include/c++/6/experimental/optional:723:7: error: forming pointer to reference type ‘Octree&’
       operator->()
       ^~~~~~~~
parc.cpp: In member function ‘std::experimental::fundamentals_v1::optional<Octree&> Octree::operator[](int)’:
parc.cpp:31:10: error: could not convert ‘&((Octree*)this)->Octree::branch[index]’ from ‘Octree**’ to ‘std::experimental::fundamentals_v1::optional<Octree&>’
   return &branch[index];
          ^~~~~~~~~~~~~~
parc.cpp: In function ‘int main(int, char**)’:
parc.cpp:41:24: error: no match for ‘operator=’ (operand types are ‘std::experimental::fundamentals_v1::optional<Octree&>’ and ‘Octree*’)
      o[0] = new Octree();
                        ^
In file included from parc.cpp:1:0:
/usr/include/c++/6/experimental/optional:595:7: note: candidate: std::experimental::fundamentals_v1::optional<_Tp>& std::experimental::fundamentals_v1::optional<_Tp>::operator=(std::experimental::fundamentals_v1::nullopt_t) [with _Tp = Octree&]
       operator=(nullopt_t) noexcept
       ^~~~~~~~
/usr/include/c++/6/experimental/optional:595:7: note:   no known conversion for argument 1 from ‘Octree*’ to ‘std::experimental::fundamentals_v1::nullopt_t’
/usr/include/c++/6/experimental/optional:609:9: note: candidate: template<class _Up> std::enable_if_t<std::__and_<std::__not_<std::is_same<std::experimental::fundamentals_v1::optional<_Tp>, typename std::decay<_Up>::type> >, std::is_constructible<_Tp, _Up>, std::__not_<std::__and_<std::is_scalar<_Tp>, std::is_same<_Tp, typename std::decay<_Up>::type> > >, std::is_assignable<_Tp&, _Up> >::value, std::experimental::fundamentals_v1::optional<_Tp>&> std::experimental::fundamentals_v1::optional<_Tp>::operator=(_Up&&) [with _Up = _Up; _Tp = Octree&]
         operator=(_Up&& __u)
         ^~~~~~~~
/usr/include/c++/6/experimental/optional:609:9: note:   template argument deduction/substitution failed:
/usr/include/c++/6/experimental/optional:628:9: note: candidate: template<class _Up> std::enable_if_t<std::__and_<std::__not_<std::is_same<_T1, _U1> >, std::is_constructible<_Tp, const _Up&>, std::is_assignable<_Tp&, _Up>, std::__not_<std::__or_<std::is_constructible<_Tp, const std::experimental::fundamentals_v1::optional<_Up>&>, std::is_constructible<_Tp, std::experimental::fundamentals_v1::optional<_Up>&>, std::is_constructible<_Tp, const std::experimental::fundamentals_v1::optional<_Up>&&>, std::is_constructible<_Tp, std::experimental::fundamentals_v1::optional<_Up>&&>, std::is_convertible<const std::experimental::fundamentals_v1::optional<_Up>&, _Tp>, std::is_convertible<std::experimental::fundamentals_v1::optional<_Up>&, _Tp>, std::is_convertible<const std::experimental::fundamentals_v1::optional<_Up>&&, _Tp>, std::is_convertible<std::experimental::fundamentals_v1::optional<_Up>&&, _Tp> > >, std::__not_<std::__or_<std::is_assignable<_Tp&, const std::experimental::fundamentals_v1::optional<_Up>&>, std::is_assignable<_Tp&, std::experimental::fundamentals_v1::optional<_Up>&>, std::is_assignable<_Tp&, const std::experimental::fundamentals_v1::optional<_Up>&&>, std::is_assignable<_Tp&, std::experimental::fundamentals_v1::optional<_Up>&&> > > >::value, std::experimental::fundamentals_v1::optional<_Tp>&> std::experimental::fundamentals_v1::optional<_Tp>::operator=(const std::experimental::fundamentals_v1::optional<_Up>&) [with _Up = _Up; _Tp = Octree&]
         operator=(const optional<_Up>& __u)
         ^~~~~~~~
/usr/include/c++/6/experimental/optional:628:9: note:   template argument deduction/substitution failed:
parc.cpp:41:24: note:   mismatched types ‘const std::experimental::fundamentals_v1::optional<_Tp>’ and ‘Octree*’
      o[0] = new Octree();
                        ^
In file included from parc.cpp:1:0:
/usr/include/c++/6/experimental/optional:653:9: note: candidate: template<class _Up> std::enable_if_t<std::__and_<std::__not_<std::is_same<_T1, _U1> >, std::is_constructible<_Tp, _Up>, std::is_assignable<_Tp&, _Up>, std::__not_<std::__or_<std::is_constructible<_Tp, const std::experimental::fundamentals_v1::optional<_Up>&>, std::is_constructible<_Tp, std::experimental::fundamentals_v1::optional<_Up>&>, std::is_constructible<_Tp, const std::experimental::fundamentals_v1::optional<_Up>&&>, std::is_constructible<_Tp, std::experimental::fundamentals_v1::optional<_Up>&&>, std::is_convertible<const std::experimental::fundamentals_v1::optional<_Up>&, _Tp>, std::is_convertible<std::experimental::fundamentals_v1::optional<_Up>&, _Tp>, std::is_convertible<const std::experimental::fundamentals_v1::optional<_Up>&&, _Tp>, std::is_convertible<std::experimental::fundamentals_v1::optional<_Up>&&, _Tp> > >, std::__not_<std::__or_<std::is_assignable<_Tp&, const std::experimental::fundamentals_v1::optional<_Up>&>, std::is_assignable<_Tp&, std::experimental::fundamentals_v1::optional<_Up>&>, std::is_assignable<_Tp&, const std::experimental::fundamentals_v1::optional<_Up>&&>, std::is_assignable<_Tp&, std::experimental::fundamentals_v1::optional<_Up>&&> > > >::value, std::experimental::fundamentals_v1::optional<_Tp>&> std::experimental::fundamentals_v1::optional<_Tp>::operator=(std::experimental::fundamentals_v1::optional<_Up>&&) [with _Up = _Up; _Tp = Octree&]
         operator=(optional<_Up>&& __u)
         ^~~~~~~~
/usr/include/c++/6/experimental/optional:653:9: note:   template argument deduction/substitution failed:
parc.cpp:41:24: note:   mismatched types ‘std::experimental::fundamentals_v1::optional<_Tp>’ and ‘Octree*’
      o[0] = new Octree();
                        ^
In file included from parc.cpp:1:0:
/usr/include/c++/6/experimental/optional:493:11: note: candidate: std::experimental::fundamentals_v1::optional<Octree&>& std::experimental::fundamentals_v1::optional<Octree&>::operator=(const std::experimental::fundamentals_v1::optional<Octree&>&)
     class optional
           ^~~~~~~~
/usr/include/c++/6/experimental/optional:493:11: note:   no known conversion for argument 1 from ‘Octree*’ to ‘const std::experimental::fundamentals_v1::optional<Octree&>&’
/usr/include/c++/6/experimental/optional:493:11: note: candidate: std::experimental::fundamentals_v1::optional<Octree&>& std::experimental::fundamentals_v1::optional<Octree&>::operator=(std::experimental::fundamentals_v1::optional<Octree&>&&)
/usr/include/c++/6/experimental/optional:493:11: note:   no known conversion for argument 1 from ‘Octree*’ to ‘std::experimental::fundamentals_v1::optional<Octree&>&&’
/usr/include/c++/6/experimental/optional: In instantiation of ‘void std::experimental::fundamentals_v1::_Optional_base<_Tp, false>::_M_construct(_Args&& ...) [with _Args = {Octree}; _Tp = Octree&]’:
/usr/include/c++/6/experimental/optional:384:11:   required from ‘std::experimental::fundamentals_v1::_Optional_base<_Tp, false>::_Optional_base(std::experimental::fundamentals_v1::_Optional_base<_Tp, false>&&) [with _Tp = Octree&]’
/usr/include/c++/6/experimental/optional:493:11:   required from here
/usr/include/c++/6/experimental/optional:439:11: error: new cannot be applied to a reference type
           ::new (std::__addressof(this->_M_payload))
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
             _Stored_type(std::forward<_Args>(__args)...);
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Я уверен, что есть способ перегрузить присвоение, чтобы я мог вернуть необязательное и затем присвоить ему, как я это делал в main выше. Спасибо за любые указатели! ; -)

1 Ответ

2 голосов
/ 14 июня 2019

Цель (i) может быть достигнута путем возврата вспомогательного класса, который перегружает оператор =. Цель (ii) может быть достигнута путем возврата вспомогательного класса, который перегружает оператор bool.

Посмотрите, что происходит, когда ваш operator[] возвращает класс, который выглядит следующим образом:

class Octree {

    // Other declarations...

public:

    // Other declarations...

    struct value_at {

        Octree *ptr;

        operator bool() const { return ptr != nullptr; }

        Octree &operator=(const Octree &v)
        {
            return *ptr=v;
        }
    };

    value_at operator[](int index);
};

Построение value_at будет вашим домашним заданием; но очевидно, что возвращаемый объект с нулевым ptr представляет несуществующее значение, в противном случае он указывает на возвращаемое значение.

Теперь ваши операторы [] могут использоваться в логическом контексте, который оценивает указание того, было ли возвращено значение или нет, а присвоение чего-либо возвращаемому значению заканчивается присвоением значения, которое [] якобы вернулся.

Перегрузка оператора = может также проверить, является ли ptr нулем, и выдать исключение в качестве средства отладки.

Вспомогательный класс также может объявлять перегрузку operator Octree() const, так что возвращаемый объект выглядит еще более прозрачным.

Сказав все вышеизложенное: вы также можете вернуть std::optional<std::reference_wrapper<Octree>>, который на самом деле ближе к объекту, описанному в вашем вопросе. Однако использование его на практике может потребовать некоторого громоздкого синтаксиса (назначение такого std::optional может не обязательно иметь тот эффект, который вы ищете). Такой простой вспомогательный класс обычно приводит к более естественному и прозрачному использованию.

...