Enlace estructurado C++17 que también incluye una variable existente

5 minutos de lectura

Avatar de usuario de Quuxplusone
Quuxplusone

Esta respuesta SO enumera algunas deficiencias de las declaraciones de descomposición de C++ 17 (la característica anteriormente conocida como “enlace estructurado”). Por ejemplo, no puede dar tipos explícitos a las nuevas variables, etc. Pero una gran deficiencia con la que me estoy topando no se menciona allí, así que me pregunto si hay una solución alternativa conocida en la que simplemente no estoy pensando.

Considerar este código de análisis JSON (que puede contener otros errores; ignórelos a los efectos de esta pregunta):

using Value = std::any;
using String = std::string;
using Object = std::map<String, Value>;

std::pair<String, const char *> load_string(const char *p, const char *end);
std::pair<Value, const char *> load_value(const char *p, const char *end);
const char *skip_spaces(const char *p, const char *end);

std::pair<Object, const char *> load_object(const char *p, const char *end)
{
    p = skip_spaces(p, end);
    if (p == end || *p++ != '{') throw ParseError("Expected {");
    p = skip_spaces(p, end);
    Object result;
    if (p == end && *p == '}') {
        // the object has no key-value pairs at all
    } else {
        while (true) {
            auto [key, p] = load_string(p, end);
            p = skip_spaces(p, end);
            if (p == end || *p++ != ':') throw ParseError("Expected :");
            auto [value, p] = load_value(p, end);
            result.insert_or_assign(std::move(key), std::move(value));
            p = skip_spaces(p, end);
            if (p == end) throw ParseError("Expected , or }");
            if (*p == '}') break;
            if (*p++ != ',') throw ParseError("Expected , or }");
        }
    }
    return {result, p+1};
}

Esto funcionaría muy bien, excepto que las líneas que comienzan auto [key, p] = y auto [value, p] = no son válidos! La variable p ya ha sido declarado. estoy tratando de asignar p un nuevo valorpero no quiero crear un nuevo variable local.

Preferiría no usar std::tie(key, p) =porque eso me obliga a dar una declaración para key antes de la asignación. Esta es la vieja y conocida objeción a std::tie. ¡Lo cual podría haber jurado que fue la razón por la que se introdujo el enlace estructurado en el lenguaje!

Entonces, ¿hay alguna solución? ¿Alguna forma agradable y limpia de escribir la construcción de combinación?key-en-lugar-y-también-asignar-a-p que expresa mi intención?

Es extraño cómo nunca me perdí esta función antes, pero tan pronto como me das un enlace estructurado para jugar, lo primero que intento no funciona. 🙁

Avatar de usuario de Catriel
catriel

#include <iostream>
#include <limits>
#include <tuple>

int main()
{
    auto step = std::numeric_limits<double>::infinity();
    auto as = std::numeric_limits<int>::infinity();

    std::tie(step, as) = std::tuple{ 0.1, 2 };
    std::cout << step << ", " << as << std::endl;
}

En casos con tipos más complejos, una solución simple de un nuevo objeto temporal móvil podría ser el paso deseable más simple en la dirección de lo que desea (aunque creo que en su caso particular podría ser más simple apegarse a tradicional tie en cambio):

... // (as in your code: p & end exist already, key & p_ not yet)

auto [key, p_] = load_string(p, end);
p = move(p_);

... // (continue using p)

Lo siento, finalmente no pude compilarlo yo mismo, aunque pude ver que era un problema de mi IDE (CLion, que actualmente solo admite la mitad de C ++ 17), aunque espero que funcione en general.

Avatar de usuario de Quuxplusone
Quuxplusone

Esta es una idea realmente tonta que no sugeriría seriamente a menos que termine siendo no solución sana… pero considere el siguiente código.

template<size_t P, size_t... Is>
auto plus(std::index_sequence<Is...>)
{
    return std::index_sequence<P+Is...>{};
}

template<typename RHS, size_t... Is>
auto tuple_select(RHS&& rhs, std::index_sequence<Is...>)
{
    return std::forward_as_tuple(std::get<Is>(std::forward<RHS>(rhs))...);
}

template<typename... Ts>
struct AndTie {
    std::tuple<Ts&...> v;
    AndTie(Ts&... vs) : v(vs...) {}

    template<typename RHS>
    auto operator=(RHS&& rhs) && {
        constexpr int N = std::tuple_size_v<RHS>;
        constexpr int K = sizeof...(Ts);
        v = tuple_select(std::forward<RHS>(rhs), plus<N-K>(std::make_index_sequence<K>{}));
        return tuple_select(std::forward<RHS>(rhs), std::make_index_sequence<N-K>{});
    }
};

esto nos da

auto [key] =AndTie(p)= load_string(p, end);
auto [value] =AndTie(p)= load_value(p, end);

Todavía tiene la limitación de que los valores l “atados” están obligados a aparecer último y las variables “declaradas” están restringidas para aparecer primero, pero no creo que haya mucha manera de evitar eso. y algo como tuple_shuffle<Is...> podría manejar eso si lo necesitaras.

  • Sería mejor si la declaración de descomposición auto [a1, a2, ..., an] = s; extraer exactamente primero n elementos, sin importar si su número es >n.

    – Tomilov Anatoliy

    5 de marzo de 2017 a las 6:59

  • @Orient: estoy de acuerdo; pero incluso si eso fuera cierto, no lo sería directamente resolver mi problema, ¿lo haría?

    – Quuxplusone

    5 de marzo de 2017 a las 19:06

  • Ahora pienso – no. Se necesita una capacidad equivalente a “desempaquetar” miembros de datos directamente en la lista de argumentos de funciones.

    – Tomilov Anatoliy

    5 de marzo de 2017 a las 19:18

¿Ha sido útil esta solución?