¿Cómo crear una nueva variable y usarla en std::tie al mismo tiempo?

4 minutos de lectura

avatar de usuario de wyer33
wyer33

¿Hay una buena manera de usar std::tie y crear una nueva variable de una sola vez? En otras palabras, si una función devuelve un std::tuple y queremos finalmente dividir el resultado en componentes individuales, ¿hay alguna manera de hacer estas asignaciones sin definir las variables de antemano?

Por ejemplo, considere el siguiente código:

#include <tuple>

struct Foo {
    Foo(int) {}
};
struct Bar{};

std::tuple <Foo,Bar> example() {
    return std::make_tuple(Foo(1),Bar()); 
}

int main() {
    auto bar = Bar {};

    // Without std::tie
    {
        auto foo_bar = example();
        auto foo = std::get<0>(std::move(foo_bar));
        bar = std::get<1>(std::move(foo_bar));
    }

    // With std::tie
    #if 0
    {
        // Error: no default constructor
        Foo foo;
        std::tie(foo,bar) = example();
    }
    #endif

}

Básicamente, la función example devuelve una tupla. Ya tenemos una variable de tipo Bar que queremos asignar, pero necesitamos una nueva variable de tipo Foo. Sin std::tieno necesitamos crear una instancia ficticia de Foopero el código requiere que pongamos todo en un std::tuple primero y luego dividirlo. Con std::tietenemos que asignar un dummy Foo primero, pero no tenemos un constructor predeterminado para hacerlo. En realidad, pretendemos que los constructores de Foo son complicados, por lo que no es deseable crear primero un valor ficticio. En última instancia, nos gustaría asignar en ambos foo y barpero quiere hacer esta asignación y asignar memoria para Foo al mismo tiempo.

  • me duele la cabeza al leer el código, que es una señal segura de que se requiere una lógica más simple. La respuesta corta es no. devolver una tupla de valores es una forma muy eficiente de devolver y asignar. tie:: solo está ahí para desempaquetar valores. Si debe hacer esto, considere atarlo a un boost::optional<Foo>

    -Richard Hodges

    28 de marzo de 2015 a las 0:31

  • El hecho de que Bar contiene una Foo parece ser una configuración para una falla. Una vez que te divorcies (extracto) Foo de Barque el 50% de Bar se convierte en “válido pero potencialmente indeterminado / no especificado de otra manera”.

    – mal

    28 de marzo de 2015 a las 0:35

  • Una forma de evitar la necesidad de separarlos es para cualquier otra clase que solía dividir un Foo a partir de una Bar simplemente aceptar un Bar y usar su miembro Fooo para mantener Bar vivo y pasar referencias a Foo.

    – mal

    28 de marzo de 2015 a las 0:37

  • Hubo una discusión relacionada sobre una posible propuesta relacionada con este problema, échale un vistazo aquí: groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/…

    – Mikael Personson

    28 de marzo de 2015 a las 4:56

  • Debido a que la ‘asignación de memoria’ generalmente se refiere a la asignación de memoria dinámica, reformulé la pregunta en términos de definición/creación de variables. ¿Puedo sugerirle que reduzca su ejemplo? Creo que muchas cosas son innecesarias. Creo que la esencia de lo que buscas se puede resumir en algo como esto.

    –Luc Danton

    28 de marzo de 2015 a las 12:32

Avatar de usuario de Moncef Mechri
Moncef Mechri

Esta característica se llama encuadernaciones estructuradas en C++17. Muy bienvenida adición!

Ejemplo de uso:

#include <iostream>
#include <tuple>

int main()
{
    auto tuple = std::make_tuple(1, 'a', 2.3);

    // unpack the tuple into individual variables declared at the call site
    auto [ i, c, d ] = tuple;

    std::cout << "i=" << i << " c=" << c << " d=" << d << '\n';

    return 0;
}

Probado en GCC 7.2 con -std=c++17.

  • Impresionante característica.

    – Ciro Santilli OurBigBook.com

    23 de diciembre de 2017 a las 19:15

avatar de usuario de wyer33
wyer33

@MikaelPersson tenía el enlace correcto. Básicamente, no hay una gran manera de hacer esto. Sin embargo, hay algunas formas inteligentes basadas en N3802. Es decir, utilizar

// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
    using Indices =
        std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
    return apply_impl(std::forward<F>(f), std::forward<Tuple>
}

A continuación, escribir

// With compose
{
    auto foo = apply([&bar](auto && foo,auto && bar_) {
        bar=std::move(bar_);
        return std::move(foo);
    }, example());
}

Y, sí, todo esto es feo, pero la situación surgió en algún momento que tuve. Sin embargo, como muestra el enlace de @MikaelPersson, este es un problema general y aún no está completamente resuelto.

¿Ha sido útil esta solución?