Un recursive_transform pour std :: vector avec différents types de retour

Oct 25 2020

C'est la question de suivi pour une fonction recursive_transform pour différents types Iterable imbriqué avec l'implémentation std :: variant en C ++ et une fonction get_from_variant en C ++ . Merci à G. Sliepen et Quuxplusone de fournir des suggestions de révision détaillées. Cependant, la version existante de recursive_transformfunction suppose que le type de retour est toujours le même que le type d'entrée. En d'autres termes, cela fonctionne bien avec la fonction lambda comme [](double x)->double { return x + 1; }(le type d'entrée et de sortie sont double) ou [](int x)->int { return x + 1; }(le type d'entrée et de sortie sont int). Dans l'étape suivante, je souhaite me concentrer sur le cas où le type de retour est différent du type d'entrée. Par exemple,[](int x)->std::string { return std::to_string(x); }. Étant donné que le type de retour d'origine de recursive_transformest spécifié dans T, il ne peut pas gérer le cas dans lequel le type de la sortie traitée de la fonction lambda fest différent de T. Soit le type de changement de Tdans autocomme ci - dessous. Cette autosyntaxe utilisée ici rend le type dérivant adaptatif.

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

L'autre partie de ceci recursive_transformest la structure récursive et elle est plus complexe que ci-dessus. Le conteneur a été spécifié std::vectorici en premier.

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

Le test de la fonction de modèle ci-dessus 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 lien Godbolt est ici.

Toutes les suggestions sont les bienvenues.

  • À quelle question fait-il suite?

    Une fonction recursive_transform pour divers types Iterable imbriqués avec implémentation std :: variant en C ++ et

    Une fonction get_from_variant en C ++

  • Quelles modifications ont été apportées au code depuis la dernière question?

    Dans la version précédente de recursive_transformfunction, cela fonctionne bien lorsque le type de retour est identique au type d'entrée. L'idée principale de cette question est d'essayer d'implémenter une version étendue dont le type de retour est différent du type d'entrée.

  • Pourquoi un nouvel examen est-il demandé?

    La recursive_transformfonction de la version précédente suppose que le type de retour est toujours le même que le type d'entrée. J'essaie de me concentrer sur le cas où le type de retour est différent du type d'entrée pour rendre la fonction plus générique. Cependant, je sais que je rend mes algorithmes plus spécialisés à std::vectornouveau dans cette version de code. Je ne sais pas comment implémenter une recursive_transformfonction plus générique à la fois dans divers types de sortie et divers types de conteneurs d'une manière simple et intelligente. S'il y a une suggestion ou une idée possible à ce sujet, veuillez me le faire savoir.

Réponses

3 G.Sliepen Oct 25 2020 at 23:24

J'ai peur que cela n'atteigne les limites de mes connaissances sur les modèles en C ++, mais je vais essayer d'y répondre quand même du mieux que je peux:

Utilisez std::back_inserter()pour remplir les vecteurs

Au lieu de construire un vecteur d'une taille donnée, déclarez simplement un vecteur vide, mais réservez suffisamment de capacité, puis utilisez std::back_inserter()pour le remplir:

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

Cependant, si vous n'utilisez pas std::vectormais un type de conteneur différent, reserve()et / ou std::back_inserter()peut ne pas être approprié.

Déterminer le type de conteneur

Donc, idéalement, nous ne voulons pas déclarer a std::vector, mais plutôt le conteneur externe de type T. Vous pouvez utiliser des paramètres de modèle de modèle pour déconstruire des types de modèles:

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;
    ...
}

Malheureusement, cela ne fonctionne pas, du moins pas avec Clang, car a a std::vectoren fait deux paramètres de modèle et d'autres conteneurs peuvent avoir plus ou moins de paramètres de modèle. La solution à ce problème consiste donc à déclarer ValueTypecomme pack de paramètres de modèle:

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;
    ...
}

Bien que, bien sûr, cela ne transfère pas le deuxième paramètre de modèle. Tout rassembler jusqu'à présent:

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