web-dev-qa-db-fra.com

Appeler une fonction pour chaque argument de template variadic et un tableau

J'ai donc un type X:

typedef ... X;

et une fonction template f:

class <typename T>
void f(X& x_out, const T& arg_in);

puis une fonction g:

void g(const X* x_array, size_t x_array_size);

J'ai besoin d'écrire une fonction de modèle variadique h qui fait ceci:

template<typename... Args>
void h(Args... args)
{
    constexpr size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    for (int i = 0; i < nargs; i++) // foreach arg
        f(x_array[i], args[i]); // call f (doesn't work)

    g(x_array, nargs); // call g with x_array
}

Cela ne fonctionne pas parce que vous ne pouvez pas utiliser de tels arguments au moment de l'exécution.

Quelle est la meilleure technique pour remplacer la partie centrale de h?

Et le gagnant est Xeo:

template<class T> X fv(const T& t) { X x; f(x,t); return x; }

template<class... Args>
void h(Args... args)
{
  X x_array[] = { fv(args)... };

  g(x_array, sizeof...(Args));
}

(En fait, dans mon cas particulier, je peux réécrire f pour renvoyer x par valeur plutôt que comme paramètre out, je n'ai donc même pas besoin de fv ci-dessus)

25
Andrew Tomazos

Vous pouvez refactoriser ou encapsuler f pour renvoyer une nouvelle X au lieu de la faire passer, car cela jouerait une extension de pack dans la main et rendrait la fonction vraiment concise:

template<class T>
X fw(T const& t){ X x; f(x, t); return x; }

template<class... Args>
void h(Args... args){
  X xs[] = { fw(args)... };
  g(xs, sizeof...(Args));
}

Exemple en direct.

Et si vous pouviez changer g pour accepter simplement un std::initializer_list, cela deviendrait encore plus concis:

template<class... Args>
void h(Args... args){
  g({f(args)...});
}

Exemple en direct. Ou (peut-être mieux), vous pouvez également fournir uniquement un wrapper g qui transmet à la vraie g:

void g(X const*, unsigned){}

void g(std::initializer_list<X> const& xs){ g(xs.begin(), xs.size()); }

template<class... Args>
void h(Args... args){
  g({f(args)...});
}

Exemple en direct.
Edit: Une autre option utilise un tableau temporaire:

template<class T>
using Alias = T;

template<class T>
T& as_lvalue(T&& v){ return v; }

template<class... Args>
void h(Args... args){
  g(as_lvalue(Alias<X[]>{f(args)...}), sizeof...(Args));
}

Exemple en direct. Notez que la fonction as_lvalue est dangereuse, le tableau ne vit toujours que jusqu'à la fin de l'expression complète (dans ce cas, g), soyez donc prudent lorsque vous l'utilisez. La Alias est nécessaire car seul X[]{ ... } n'est pas autorisé en raison de la grammaire linguistique.

Si tout cela n'est pas possible, vous aurez besoin de la récursivité pour accéder à tous les éléments du pack args.

#include <Tuple>

template<unsigned> struct uint_{}; // compile-time integer for "iteration"

template<unsigned N, class Tuple>
void h_helper(X (&)[N], Tuple const&, uint_<N>){}

template<unsigned N, class Tuple, unsigned I = 0>
void h_helper(X (&xs)[N], Tuple const& args, uint_<I> = {}){
  f(xs[I], std::get<I>(args));
  h_helper(xs, args, uint_<I+1>());
}

template<typename... Args>
void h(Args... args)
{
    static constexpr unsigned nargs = sizeof...(Args);
    X xs[nargs];

    h_helper(xs, std::tie(args...));

    g(xs, nargs);
}

Exemple en direct.

Edit: Inspiré par le commentaire d’ecatmur, j’ai utilisé l’astuce index pour le faire fonctionner avec juste une extension de paquetage et avec f et g tels quels, sans les modifier.

template<unsigned... Indices>
struct indices{
  using next = indices<Indices..., sizeof...(Indices)>;
};
template<unsigned N>
struct build_indices{
  using type = typename build_indices<N-1>::type::next;
};
template <>
struct build_indices<0>{
  using type = indices<>;
};
template<unsigned N>
using IndicesFor = typename build_indices<N>::type;

template<unsigned N, unsigned... Is, class... Args>
void f_them_all(X (&xs)[N], indices<Is...>, Args... args){
  int unused[] = {(f(xs[Is], args), 1)...};
  (void)unused;
}

template<class... Args>
void h(Args... args){
  static constexpr unsigned nargs = sizeof...(Args);
  X xs[nargs];
  f_them_all(xs, IndicesFor<nargs>(), args...);
  g(xs, nargs);
}

Exemple en direct.

24
Xeo

C'est évident: vous n'utilisez pas l'itération mais la récursion. Lorsque vous manipulez des modèles variés, quelque chose récursif entre toujours. Même lorsque vous liez les éléments à un std::Tuple<...> à l'aide de tie(), il est récursif: il arrive que l'activité récursive soit effectuée par le Tuple. Dans votre cas, il semble que vous souhaitiez quelque chose comme ceci (il y a probablement quelques fautes de frappe mais dans l'ensemble cela devrait fonctionner):

template <int Index, int Size>
void h_aux(X (&)[Size]) {
}

template <int Index, int Size, typename Arg, typename... Args>
void h_aux(X (&xs)[Size], Arg arg, Args... args) {
    f(xs[Index], arg);
    h_aux<Index + 1, Size>(xs, args...);
}

template <typename... Args>
void h(Args... args)
{
    X xs[sizeof...(args)];
    h_aux<0, sizeof...(args)>(xs, args...);
    g(xs, sizeof...(args));
}

Je pense que vous ne pourrez pas utiliser nargs pour définir la taille du tableau: rien n'indique au compilateur qu'il s'agisse d'une expression constante.

6
Dietmar Kühl

C'est assez simple à faire avec le développement du pack de paramètres, même si vous ne pouvez pas réécrire f pour renvoyer le paramètre de sortie par valeur:

struct pass { template<typename ...T> pass(T...) {} };

template<typename... Args>
void h(Args... args)
{
    const size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    X *x = x_array;
    int unused[]{(f(*x++, args), 1)...}; // call f
    pass{unused};

    g(x_array, nargs); // call g with x_array
}

Il devrait être possible d'écrire

    pass{(f(*x++, args), 1)...}; // call f

mais il semble que g ++ (au moins 4.7.1) ait un bogue qui ne permet pas de commander l'évaluation des paramètres de liste d'initialisation de brace en tant qu'initialiseurs de classe. Les initialiseurs de tableaux sont OK cependant; voir Séquence dans une extension variadique pour plus d'informations et d'exemples.

Exemple live .


Comme alternative, voici la technique mentionnée par Xeo utilisant un pack d’index généré; malheureusement, cela nécessite un appel de fonction supplémentaire et un paramètre, mais il est assez élégant (surtout si vous avez un générateur de packs d'index qui traîne):

template<int... I> struct index {
    template<int n> using append = index<I..., n>; };
template<int N> struct make_index { typedef typename
    make_index<N - 1>::type::template append<N - 1> type; };
template<> struct make_index<0> { typedef index<> type; };
template<int N> using indexer = typename make_index<N>::type;

template<typename... Args, int... i>
void h2(index<i...>, Args... args)
{
    const size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    pass{(f(x_array[i], args), 1)...}; // call f

    g(x_array, nargs); // call g with x_array
}

template<typename... Args>
void h(Args... args)
{
  h2(indexer<sizeof...(args)>(), std::forward<Args>(args)...);
}

Voir C++ 11: Je peux passer de plusieurs arguments à Tuple, mais puis-je passer de Tuple à plusieurs arguments? pour plus d'informations . Exemple en direct .

4
ecatmur

Beau modèle comme réponse pour la première partie de la question:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) {
    [](...){}((f(std::forward<Args>(args)), 0)...);
}
4
Victor Laskin

Xeo est sur la bonne idée - vous voulez construire une sorte "d'itérateur variadique" qui cache beaucoup de cette méchanceté du reste du code.

Je prendrais le contenu de l'index et le cacherais derrière une interface d'itérateur modélisée d'après std :: vector, puisqu'un std :: Tuple est aussi un conteneur linéaire pour les données. Ensuite, vous pouvez simplement réutiliser toutes vos fonctions et classes variadiques sans avoir à avoir de code explicitement récursif ailleurs.

0
Zack Yezek