Избегание копий в операторе преобразования в подмножество базовых классов - PullRequest
4 голосов
/ 11 апреля 2019

Прежде всего: вы будете кричать " XY проблема !"и вы будете правы, но сейчас я пытаюсь увидеть, есть ли хорошее решение для этого конкретного Y, чтобы судить / минимизировать его компромиссы по сравнению с другими Y для этого (большого) X.


Рассмотрим следующий класс шаблона переменных, который наследует от всех своих аргументов шаблона и предоставляет оператор преобразования для их подмножества:

template <typename... Ts>
struct derived : Ts...
{
    template<class... SubTs>
    operator const derived<SubTs...>()
    {
        return { static_cast<SubTs>(*this)... };
    }
};

Это позволяет вам сделать что-то вроде этого:

struct A { int a; };
struct B { double b; };
struct C { std::unique_ptr<int> c; };

using ABC = derived<A, B, C>;

int foo(const derived<A, B>& in)
{
    return in.a + in.b;
}

int test()
{
    ABC abc{ {1}, {3.0}, {std::make_unique<int>(4)}};
    return foo(x);
}

Преобразование создает временную копию для передачи функциям.Таким образом, вы не можете сделать следующее, потому что unique_ptr не может быть скопировано:

int bar(const derived<A, C>& in)
{
    return in.a + *in.c;
}

int test()
{
    ABC abc { {1}, {3.0}, {std::make_unique<int>(4)}};
    return bar(x);
}

Живой код: https://godbolt.org/z/KBoBDl

Есть ли решение, где:

  1. Я могу передать не копируемое derived (без отказа от владения, конечно).

  2. Мне не нужно указывать, какой из A / B / C данный член in из (нет in.get<A>().a).«Использовать композицию вместо наследования» также не является решением .Другими словами, семантика in.a (in->a также будет в порядке) должна быть сохранена.

  3. Аргументы функции для foo и bar прописывают суб-типы, доступные в их телах в едином списке вариационных шаблонов (не обязательно должны быть в derived, хотя я чувствую, что предыдущее требование форсирует это).Если я не упомяну B, то функция не должна иметь доступа к B членам (даже если переданный в derived содержит B).

Изменение foo / bar на функции шаблона будет неудачей, но допустимо.Обратите внимание, что template<class T> int foo(T in) (и удаление оператора преобразования) нарушает последнее требование.Также обратите внимание, что я нахожусь на C ++ 17, поэтому понятия не имею.

В идеале я хотел бы представить сигнатуру функции, чтобы она выглядела как int bar(derived_view<A, C> in), но я не вижу, как объединить это со всеми требованиями,Приветствуются частичные идеи / решения, может быть, это поможет.

derived - мой лучший способ создания различных вариаций, A / B/ C уже все структуры-обёртки.Вот почему не должно быть никаких дополнительных указаний на доступ к фактическим данным.


Да, я знаю, "это звучит как нормальные параметры функции с дополнительными шагами!" , но у меня есть множество foo и bar, которые принимают множество различных подмножеств из десятков структур A / B / C и т. д., каждая из которых строит больше таких структур.Простые функциональные параметры являются хорошо изученным статус-кво и не являются удовлетворительным решением.

1 Ответ

0 голосов
/ 11 апреля 2019

Если мы ограничим derived_view значением «можно использовать только в качестве аргумента функции», тогда будет безопасно переместить любые используемые параметры из переданного derived (или derived_view) в его конструкторе и переместить их обратно.в деструкторе:

template <typename... Ts>
struct derived_view : Ts...
{
    template<class... SubTs>
    derived_view(derived<SubTs...>& original)
      : Ts(static_cast<Ts&&>(original))...
    {
        _restoreOriginal = [&](){
            ((static_cast<Ts&>(original) = static_cast<Ts&&>(*this)), ...);
        };
    }

    // Also allow construction from another derived_view
    template<class... SubTs>
    derived_view(derived_view<SubTs...>& original)
      : Ts(static_cast<Ts&&>(original))...
    {
        _restoreOriginal = [&](){
            ((static_cast<Ts&>(original) = static_cast<Ts&&>(*this)), ...);
        };
    }

    ~derived_view()
    {
        _restoreOriginal();
    }

private:
    std::function<void()> _restoreOriginal;
};

Демо: https://wandbox.org/permlink/CVkmZc7AXSkTiJZx

Это очевидно игра с огнем, если кто-то хочет использовать derived_view и получить доступ к derived (или derived_view)он был построен в то же время.Передача этого в руки других пользователей (каков план здесь), вероятно, рано или поздно приведет к катастрофе ...

...