3
votes

C ++ itération sur tuple dans l'opérateur fold

J'ai une fonction qui prend un nombre variable d'objets, chacun d'eux a une fonction qui peut appeler un callback avec une valeur. J'ai besoin d'appeler cette fonction et de collecter les valeurs dans un tuple. C'est compliqué par le fait que la fonction réelle appelle le rappel de manière asynchrone, donc je ne peux pas m'en tirer avec un simple wrapper qui le convertit en une fonction de retour traditionnelle.

Quelque chose comme ça fonctionne, tant qu'il n'y a pas de doublon types:

template<class T>
class Foo
{
    T T_;
public:
    Foo( T t ) : t_( t ) {}

    template<class Func>
    int callMe( Func func )
    {
        func( t_ );
        return 0; // this is some sort of callback ID
    }
}

template<class... Args>
std::tuple<Args...> collect( Foo<Args>... args )
{
    std::tuple<Args...> result;
    std::vector<int> callbacks
    {
        args.callMe( [&result]( const Args& x )
        {
            std::get<Args>( result ) = x;
        } )...
    };
    return result;
}

// returns tuple<int, double, char>( 1, 2.0, '3' )
auto res = collect( Foo( 1 ), Foo( 2.0 ), Foo( '3' ) );

Mais si je veux autoriser les types répétés, je devrais introduire une séquence d'entiers d'une manière ou d'une autre. Y a-t-il un moyen de le faire sans de vilaines fonctions d'aide?


0 commentaires

3 Réponses :


4
votes

Vous pouvez utiliser std :: apply pour "itérer" sur le tuple:

template<class... Args>
std::tuple<Args...> collect( Foo<Args>... args )
{
    std::tuple<Args...> result;

    [&]<auto... Is>(std::index_sequence<Is...>)
    {
        ( args.callMe( [&result]( const Args& x )
        {
            std::get<Is>( result ) = x;
        } ), ... );
    }(std::make_index_sequence_for<Args...>{});

    return result;
}

J'ai du mal à faire en sorte que les compilateurs acceptent le code ci-dessus, cependant : https://gcc.godbolt.org/z/n53PSd

  • g ++ ICEs

  • clang ++ signale une erreur absurde


Vous pouvez introduire une séquence d'entiers dans la portée en C ++ 20 en utilisant une expression lambda :

template<class... Args>
std::tuple<Args...> collect( Foo<Args>... args )
{
    std::tuple<Args...> result;

    std::apply([&](auto&&... xs)
    {   
        (args.callMe([&](const auto& x)
        {
            xs = x;
        }), ...);
   }, result);

    return result;
}

p >


5 commentaires

J'ai édité la question, la fonction callMe réelle renvoie un identifiant de rappel qui doit également être conservé.


Vous pouvez simplement ajouter un callbacks.emplace_back (args.callMe (/*...*/)) dans l'expression de repli de mes exemples pour les faire fonctionner avec votre cas d'utilisation mis à jour.


Oh gotcha, je peux aussi renvoyer le résultat de apply et l'utiliser dans la liste d'initialiseurs, je crois. Merci, cela devrait fonctionner!


@riv: voici une solution de contournement si votre compilateur se plaint également: gcc.godbolt.org/z/MVwl -6


VS2017 semble n'avoir aucun problème. Je l'ai fait un peu différemment, en utilisant une liste d'initialiseurs vectoriels, ce qui supprime également le besoin d'un opérateur de repli: gcc. godbolt.org/z/fVcm5b



1
votes

Je ne suis pas sûr que cette fonction d'assistance ( visite ) soit moche ou pas, au moins ça me semble bien.

template<class T>
T visit(Foo<T>& t){
    T result;
    t.callMe([&](const T& x){result=x;});
    return result;
}

template<class... Args>
std::tuple<Args...> collect( Foo<Args>... args )
{
    return std::tuple{visit(args)...};
}


4 commentaires

Malheureusement, je ne peux pas faire cela, car la fonction callMe renvoie d'autres éléments que je dois conserver.


@riv peut-être devriez-vous mettre à jour la question pour contenir un exemple?


J'ai fait. L'autre réponse semble fonctionner pour mon cas, merci quand même.


@riv cela dépend de ce que vous voulez, les callbacks dans votre exemple peuvent être indépendants ou non. la visite peut être réécrite pour y correspondre. Quoi qu'il en soit, c'est bien que vous trouviez une réponse.



1
votes

Vous pouvez utiliser quelques lambdas pour obtenir ce que vous voulez:

template<class... Args>
std::tuple<Args...> collect( Foo<Args>... args )
{
    return {
        [&args]{
            Args t;
            args.callMe([&t](const Args& x){ t = x; });
            return t;
        }()...
    };
}


1 commentaires

J'ai édité le commentaire, je dois conserver les valeurs de retour de callMe. De plus, le code réel est asynchrone, donc cela ne fonctionnerait pas de toute façon.