web-dev-qa-db-de.com

Aufruf einer Funktion für jedes variadische Vorlagenargument und ein Array

Ich habe also einen Typ X:

typedef ... X;

und eine Template-Funktion f:

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

und dann eine Funktion g:

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

Ich muss eine variadische Template-Funktion h schreiben, die Folgendes tut:

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
}

Der Grund, warum es nicht funktioniert, ist, dass Sie zur Laufzeit keine Argumente wie diese subskribieren können.

Was ist die beste Technik, um den mittleren Teil von h zu ersetzen?

Und der Gewinner ist 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));
}

(In meinem speziellen Fall kann ich f so umschreiben, dass x als Wert ausgegeben wird, und nicht als out-Parameter. Ich brauche also nicht einmal fv.

25
Andrew Tomazos

Sie können f umwandeln oder umwickeln, um eine neue X zurückzugeben, anstatt sie übergeben zu haben, da dies die Packungserweiterung in die Hand spielen würde und die Funktion wirklich prägnant machen würde:

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

Live-Beispiel.

Und wenn Sie g ändern könnten, um nur einen std::initializer_list zu akzeptieren, würde dies noch präziser werden:

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

Live-Beispiel. Oder (vielleicht besser), Sie können auch nur einen Wrapper g angeben, der an die echte g weiterleitet:

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

Live-Beispiel.
Edit: Eine andere Option verwendet ein temporäres Array:

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

Live-Beispiel. Beachten Sie, dass die as_lvalue-Funktion gefährlich ist. Das Array bleibt nur bis zum Ende des vollständigen Ausdrucks (in diesem Fall g). Seien Sie daher vorsichtig, wenn Sie es verwenden. Die Variable Alias ist erforderlich, da aufgrund der Sprachgrammatik nur X[]{ ... } nicht zulässig ist.

Wenn das alles nicht möglich ist, benötigen Sie eine Rekursion, um auf alle Elemente des Pakets args zugreifen zu können.

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

Live-Beispiel.

Edit: Inspiriert von ecatmurs Kommentar verwendete ich den Indizes-Trick , damit er mit nur Pack-Erweiterungen und mit f und g so wie sie ist, ohne sie zu ändern, funktionieren lässt.

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

Live-Beispiel.

24
Xeo

Es ist offensichtlich: Sie verwenden keine Iteration, sondern Rekursion. Beim Umgang mit variadischen Templates kommt immer etwas Rekursives ins Spiel. Selbst wenn die Elemente mit tie() an einen std::Tuple<...> gebunden werden, ist dies rekursiv: Es kommt nur vor, dass das rekursive Geschäft vom Tuple erledigt wird. In Ihrem Fall scheint es so, als wollten Sie so etwas (es gibt wahrscheinlich einige Tippfehler, aber insgesamt sollte dies funktionieren):

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

Ich denke, Sie können nargs auch nicht verwenden, um die Größe des Arrays zu definieren: Nichts deutet dem Compiler an, dass es ein konstanter Ausdruck sein sollte.

6
Dietmar Kühl

Mit der Erweiterung des Parameter-Packs ist es ziemlich einfach, auch wenn Sie f nicht umschreiben können, um den Ausgabeparameter nach Wert zurückzugeben:

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
}

Es sollte möglich sein, nur zu schreiben

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

es scheint jedoch, dass g ++ (mindestens 4.7.1) einen Fehler aufweist, bei dem es nicht möglich ist, die Auswertung der Parameter der Klammerinitialisierungsliste als Klasseninitialisierer anzuordnen. Array-Initialisierer sind jedoch in Ordnung. Siehe Sequenzierung in einer variadischen Erweiterung Weitere Informationen und Beispiele.

Live-Beispiel .


Als Alternative ist hier die von Xeo erwähnte Technik, die ein generiertes Indexpaket verwendet. Leider erfordert es einen zusätzlichen Funktionsaufruf und Parameter, aber es ist einigermaßen elegant (besonders wenn Sie einen Index-Pack-Generator herumliegen lassen):

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

Siehe C++ 11: Ich kann von mehreren Argumenten zu Tuple wechseln, aber kann ich von Tuple zu mehreren Argumenten wechseln? Weitere Informationen . Live-Beispiel .

4
ecatmur

Nizza Vorlage als Antwort auf den ersten Teil der Frage:

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

Xeo ist auf die richtige Idee - Sie möchten eine Art "variadischer Iterator" erstellen, der viel von dieser Bosheit hinter dem Rest des Codes verbirgt.

Ich nehme das Indexzeug und verstecke es hinter einer Iteratorschnittstelle, die nach std :: vector modelliert ist, da ein std :: Tuple auch ein linearer Container für Daten ist. Dann können Sie einfach alle Ihre variadischen Funktionen und Klassen wiederverwenden, ohne explizit rekursiven Code an anderer Stelle zu haben.

0
Zack Yezek