Un recursive_transform per std :: vector con vari tipi di ritorno

Oct 25 2020

Questa è la domanda di follow-up per una funzione recursive_transform per vari tipi annidati iterabili con implementazione std :: variant in C ++ e una funzione get_from_variant in C ++ . Grazie a G. Sliepen e Quuxplusone forniscono suggerimenti dettagliati per la revisione. Tuttavia, la versione esistente della recursive_transformfunzione presuppone che il tipo restituito sia sempre lo stesso del tipo di input. In altre parole, funziona bene con la funzione lambda come [](double x)->double { return x + 1; }(il tipo di input e output sono double) o [](int x)->int { return x + 1; }(il tipo di input e output sono int). Nel passaggio successivo, voglio concentrarmi sul caso in cui il tipo restituito è diverso dal tipo di input. Per esempio,[](int x)->std::string { return std::to_string(x); }. Poiché il tipo di restituzione dell'origine di recursive_transformè specificato in T, non può gestire il caso in cui il tipo di output elaborato dalla funzione lambda fè diverso T. Tipo di cambiamento di Let Tin autocome di seguito. Questa autosintassi qui utilizzata rende adattativa la derivazione del tipo.

template<class T, class F>
auto recursive_transform(const T& input, const F& f) {
    return f(input);
}

L'altra parte di questo recursive_transformè la struttura ricorsiva ed è più complessa di sopra. Il contenitore è stato specificato std::vectorprima qui.

template<class T, class F> requires is_iterable<T>
auto recursive_transform(const T& input, const F& f) {
    typedef typename std::iterator_traits<typename T::iterator>::value_type
        value_type;

    std::vector<decltype(recursive_transform(std::declval<value_type&&>(), f))> output(input.size());
    std::transform(input.begin(), input.end(), output.begin(),
        [f](auto& element)
        {
            return recursive_transform(element, f);
        }
    );
    return output;
}

Il test della funzione template sopra recursive_transform.

std::vector<int> test_vector = {
    1, 2, 3
};
auto recursive_transform_result = recursive_transform(
    test_vector,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing

std::cout << "string: " + recursive_transform_result.at(0) << std::endl;            //  recursive_transform_result.at(0) is a std::string

std::vector<decltype(test_vector)> test_vector2 = {
    test_vector, test_vector, test_vector
};

auto recursive_transform_result2 = recursive_transform(
    test_vector2,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing

std::cout << "string: " + recursive_transform_result2.at(0).at(0) << std::endl;     // recursive_transform_result.at(0).at(0) is also a std::string

Un collegamento Godbolt è qui.

Tutti i suggerimenti sono ben accetti.

  • A quale domanda è un follow-up?

    Una funzione recursive_transform per vari tipi annidati Iterabile con std :: variant Implementation in C ++ e

    Una funzione get_from_variant in C ++

  • Quali modifiche sono state apportate al codice dall'ultima domanda?

    Nella versione precedente della recursive_transformfunzione, funziona bene quando il tipo restituito è uguale al tipo di input. L'idea principale in questa domanda è cercare di implementare una versione estesa il cui tipo restituito è diverso dal tipo di input.

  • Perché viene richiesta una nuova recensione?

    La recursive_transformfunzione della versione precedente presuppone che il tipo restituito sia sempre lo stesso del tipo di input. Sto cercando di concentrarmi sul caso in cui il tipo restituito è diverso dal tipo di input per rendere la funzione più generica. Tuttavia, so che rendo i miei algoritmi più specializzati std::vectorin questa versione di codice. Non ho idea di come implementare una recursive_transformfunzione più generica sia in vari tipi di output che in vari tipi di contenitore in modo semplice e intelligente. Se c'è qualche suggerimento o possibile idea in merito, per favore fatemelo sapere.

Risposte

3 G.Sliepen Oct 25 2020 at 23:24

Temo che questo stia raggiungendo i limiti della mia conoscenza dei modelli in C ++, ma cercherò di rispondere comunque nel miglior modo possibile:

Utilizzare std::back_inserter()per riempire i vettori

Invece di costruire un vettore di una data dimensione, basta dichiarare un vettore vuoto, ma riservare una capacità sufficiente e quindi utilizzare std::back_inserter()per riempirlo:

std::vector<decltype(recursive_transform(std::declval<value_type&&>(), f))> output;
output.reserve(input.size());

std::transform(input.begin(), input.end(), std::back_inserter(output),
    [f](auto& element)
    {
        return recursive_transform(element, f);
    }
);

Tuttavia, se non utilizzi std::vectorma un tipo di contenitore diverso reserve()e / o std::back_inserter()potrebbe non essere appropriato.

Determinazione del tipo di contenitore

Quindi, idealmente, non vogliamo dichiarare a std::vector, ma piuttosto il contenitore esterno di tipo T. È possibile utilizzare i parametri del modello di modello per decostruire i tipi basati su modelli:

template<template<class> class Container, class ValueType, class Function>
requires is_iterable<Container<ValueType>>
auto recursive_transform(const Container<ValueType> &input, const Function &f)
{
    // You want to be able to write this:
    using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
    Container<TransformedValueType> output;
    ...
}

Sfortunatamente, questo non funziona, almeno non con Clang, perché in std::vectorrealtà ha due parametri di modello e altri contenitori potrebbero avere più o meno parametri di modello. Quindi la soluzione a questo problema è dichiarare ValueTypecome pacchetto di parametri del modello:

template<template<class...> class Container, class Function, class Ts...>
requires is_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...> &input, const Function &f)
{
    using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
    Container<TransformedValueType> output;
    ...
}

Anche se ovviamente questo non inoltra il secondo parametro del modello. Mettendo tutto insieme finora:

template<typename T>
concept is_iterable = requires(T x)
{
    *std::begin(x);
    std::end(x);
};

template<class T, class Function>
auto recursive_transform(const T &input, const Function &f)
{
    return f(input);
}

template<template<class...> class Container, class Function, class... Ts>
requires is_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...> &input, const Function &f)
{
    using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
    Container<TransformedValueType> output;

    std::transform(std::begin(input), std::end(input), std::back_inserter(output),
        [&](auto &element)
        {
            return recursive_transform(element, f);
        }
    );

    return output;
}