Evitar movimientos adicionales en make_unique / make_shared / emplace / etc para estructuras que usan inicialización agregada

std::make_unique() (y funciones similares) tienen un pocoproblema:

#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() ) };
    }
}

Salida:

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

Como puede ver, tenemos un movimiento adicional que se puede evitar.Mismo problema existe conemplace() en opcional / variante / etc: si otra función devuelve el objeto, debe moverlo.

Esto puede serdirigido con un truco:

#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(); } );
}

Esto evita movimientos innecesarios (enable_if oculta el constructor a menos quef() devuelve una instancia de S). Efectivamente terminas construyendo tus valores dentro destd::variant/std::optional/ etc a través de una llamada a su función de construcción.

Esta solución tiene un pequeño problema: agregar un constructor rompe la inicialización agregada. Verejemplo. Es decir. si la estructura dada no tenía constructor y agrega uno, ya no puede inicializarlo así:

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()}; }

Pregunta: ¿Hay alguna forma de evitar este problema? Algo que no introduce nuevos constructores ...

Me gustaría poder poner eficientemente mis estructuras en opcional / variant / shared_ptr-block / etc sin romper el código (más bien no trivial) que las crea.

Respuestas a la pregunta(1)

Su respuesta a la pregunta