Подтвердить что ты не робот

Избегайте дополнительного перемещения в make_unique/make_shared/emplace/etc для структур, которые используют агрегатную инициализацию

std::make_unique() (и аналогичные функции) имеют небольшую проблему :

#include <cstdio>
#include <memory>

using namespace std;

struct S
{
    S()         { printf("ctor\n"); }
    ~S()        { printf("dtor\n"); }
    S(S const&) { printf("cctor\n"); }
    S(S&&)      { printf("mctor\n"); }
};

S foo() { return S(); }

int main()
{
    {
        printf("--------------- case 1 ---------------\n");
        unique_ptr<S> s1 = make_unique<S>( foo() );
    }

    {
        printf("--------------- case 2 ---------------\n");
        unique_ptr<S> s2 { new S( foo() ) };
    }
}

Вывод:

--------------- case 1 ---------------
ctor
mctor
dtor
dtor
--------------- case 2 ---------------
ctor
dtor

Как вы видите, у нас есть дополнительный ход, которого можно избежать. Такая же проблема существует с emplace() в опции /variant/etc - если объект возвращается другой функцией, вам нужно переместить его.

Это может быть обращено с помощью трюка:

#include <cstdio>
#include <optional>

using namespace std;

struct S
{
    S()         { printf("ctor\n"); }
    ~S()        { printf("dtor\n"); }
    S(S const&) { printf("cctor\n"); }
    S(S&&)      { printf("mctor\n"); }

    template<class F, enable_if_t<is_same_v<invoke_result_t<F>, S>>...>
    S(F&& f) : S(forward<F>(f)()) {}
};

S foo() { return S(); }

int main()
{
    optional<S> s;
    s.emplace( []{ return foo(); } );
}

Это позволяет избежать ненужного перемещения (enable_if скрывает конструктор, если f() не возвращает экземпляр S). Фактически вы создаете свои значения внутри std::variant/std::optional/etc через вызов вашей функции построения.

У этого исправления есть небольшая проблема - добавление конструктора прерывает инициализацию агрегата. См. пример. То есть если данная структура не имеет конструктора, и вы добавляете ее, вы больше не можете ее инициализировать следующим образом:

struct D
{
    float m;
    S s;

    // adding new constructor here will break existing bar() functions
};

D bar() { /*...lots of code with multiple return statements...*/ return {2.0, foo()}; }

Вопрос: Есть ли способ решить эту проблему? Что-то, что не вводит новые конструкторы...

Я хотел бы эффективно разместить мои структуры в необязательном /variant/shared _ptr-block/etc без разбивки (довольно нетривиального) кода, который их создает.

4b9b3361

Ответ 1

Вместо добавления конструктора к вашему типу, который использует функцию factory, вместо этого создайте новый внешний объект factory с оператором преобразования в ваш тип. С С++ 17 это требует минимальной работы:

template <class F>
struct factory {
    F f;

    operator invoke_result_t<F&>() { return f(); }
};

template <class F>
factory(F ) -> factory<F>;

Для вашего предыдущего примера S больше не нужен конструктор с ограничениями. Вы бы сделали:

optional<S> s;
s.emplace( factory{[]{ return foo(); }} ); // or really just factory{foo}

Что печатает только ctor и dtor. Поскольку мы никоим образом не изменяем S, мы могли бы использовать это также в агрегатах - например, D.