2013-08-01 8 views
6

Ich möchte Template-Klasse erstellen, die Funktionszeiger und Argumente für eine dieser Funktion speichern könnte, damit die Funktion später mit diesen Argumenten aufgerufen werden kann.Wie speichere ich eine variable Anzahl von Argumenten mithilfe variadischer Vorlagenargumente?

Ich möchte dies universell schreiben und nicht auf Argumenttypen oder -nummer abhängen. Hier

ist ein scatch der Idee mit der Verwendung von variadische Vorlagen von C++ 11:

template<class T, typename... Params> 
class LazyEvaluation { 
private: 
    // Function to be invoked later 
    T (*f)(Params...); 
    // Params for function f 
    Params... storedParams; // This line is not compilable! 
    bool evaluated; 
    T result; 
public: 
    // Constructor remembers function pointer and parameters 
    LazyEvaluation(T (*f)(Params...),Params... params) 
    : f(f), 
    storedParams(params) //this line also cannot be compiled 
    {} 
    // Method which can be called later to evaluate stored function with stored arguments 
    operator T&() { 
      // if not evaluated then evaluate 
      if (! evaluated) { 
        result = f(storedParams...); 
        evaluated = true; 
      } 
      return result; 
    } 
} 

Ich mag würde zumindest haben, die öffentliche Schnittstelle dieser Klasse Typen sicher, wenn es möglich ist. Obwohl es zumindest irgendwie wichtiger ist, diese Arbeit zu bekommen.

Ich habe es geschafft, die Variable Anzahl der Argumente irgendwie zu speichern. Aber ich konnte sie nicht an die Funktion f übergeben. Ich werde es in Antworten schreiben, aber ich möchte, dass Sie über Ihre eigenen Lösungen nachdenken, bevor Sie meinen hässlichen, nicht funktionierenden Versuch sehen.

Ich tring, um den obigen Code mit Microsoft Visual C++ - Compiler November 2012 CTP (v120_CTP_Nov2012) zu kompilieren, aber es wäre am besten, wenn eine Compiler-unabhängige Lösung existieren würde.

Danke

+3

Ich denke, das ist ein Duplikat: http://stackoverflow.com/questions/14833129/a-clean-way-to-store-a-function-and-its-arbitrary-type-arbitrary-number-argum – hmjd

+4

Speichern Sie sie in einem 'std :: tuple ' ... – kennytm

+0

@KennyTM Das sollte eine Antwort sein. – Angew

Antwort

0

Hier ist, wie ich versuchte, es zu lösen:

Das parametr Pack kann recursivle erweitert und jede parametr gespeichert sein. Der Funktionsspeicher soll es tun. Es verwendet eine (zweifach überladene) Hilfsfunktion.

template<typename T> 
void storeHelperFunction(void*& memory, T last) { 
    *((T*)memory) = last; 
    memory = (void*)((char*)memory + sizeof(T)); 
} 

template<typename T, typename... Params> 
void storeHelperFunction(void*& memory, T first, Params... rest) { 
    storeHelperFunction(memory, first); 
    storeHelperFunction(memory, rest...); 
} 

template<typename... Params> 
void store(void* memory, Params... args) { 
    // Copy of pointer to memory was done when passing it to this function 
    storeHelperFunction(memory, args...); 
} 

Funktion Speicher nimmt einen Zeiger auf Speicher, wo die varialbe Anzahl von Argumenten soll gerettet werden.

Der Zeiger kann auf einen dynamisch zugewiesenen Speicher oder Beter zur Struktur zeigen, deren Größe gleich sizeof...(Params) ist. Eine solche Struktur, die genau jede desiared Größe kann mit Metaprogrammierung konstruiert werden:

template <int N> 
struct allocatorStruct { 
    char byte1; 
    allocatorStruct<N-1> next; 
}; 

template <> 
struct allocatorStruct<1> {}; 

Ich bin nicht sicher, was die standart sagt oder wie die anderen Compilern als die Microsoft man es kompilieren. Aber mit meinem Compiler die Größe von (allocatorStruct) ist gleich N für jede N, die größer oder gleich 1 ist.

Daher allocatorStruct<sizeof...(Params)> hat die gleiche Größe wie Params.

Eine andere Möglichkeit, etwas zu erstellen, das die gleiche Größe wie Params hat, ist die Verwendung eines Typs char [sizeof...(Params)]. Dies hat den Nachteil, dass der Compiler nur Zeiger auf dieses Array übergibt, wenn Sie versuchen, ein solches Array als Argument zu übergeben. Deshalb ist es besser, allocatorStruct<sizeof...(Params)> zu verwenden.

Und jetzt die Hauptidee:

Wenn die Funktion Speichern wir es werfen kann: T (*)(allocatorStruct<sizeof...(Params)>). Beim Speichern der Argumente für die Funktion können wir sie in der Struktur des Typs allocatorStruct<sizeof...(Params)> speichern.

Die Größe der Argumente ist gleich. Obwohl der Funktionszeiger über den Typ der Funktion liegt, auf die die Funktion zeigt, werden ihre Daten korrekt angezeigt.

Zumindest hoffte ich.Abhängig von der Aufrufkonvention habe ich erwartet, dass die übergebenen Argumente neu geordnet oder falsch sein können, weil die Argumente von links nach rechts und von rechts nach links übergangen werden. Aber das war nicht der Fall. Bei Verwendung der __cdecl-Aufrufkonvention wurde nur das erste Argument übergeben und das andere wurde verloren. Mit anderen Aufrufkonventionen wurde das Programm beendet.

Ich habe nicht viel Zeit damit verbracht, es zu debuggen und auf Daten im Speicher (auf Stapel) zu schauen. Ist es zumindest der richtige Weg?

+0

Speicherausrichtung ist problematisch. Sie können nicht wissen, dass "memory + sizeof (T)" eine gute Ausrichtung für das nächste Element in der Argumentliste ist, – selalerer

0

einfach ein Lambda-Ausdruck

// Some function. 
int add(int a, int b) { 
    return a + b; 
} 

auto lazyFunc = [] { return add(1, 2); }; 

std::cout << lazyFunc() << std::endl; // Evaluate function and output result. 

verwenden Wenn Sie wirklich eine Klasse erstellen möchten, die nur einmal die Funktion wertet (träge), variadische Vorlagen verwenden, könnten Sie so etwas wie im folgenden Code tun.

Ich habe auch die Klasse als solche gemacht, dass Sie nicht jedes Mal, wenn sich die Parameter ändern, eine neue Instanz erstellen müssen. Ich verwende eine std::tuple, um die gegebenen Argumente zu speichern und mit vorher gegebenen Argumenten zu vergleichen. Wenn sich die Argumente unterscheiden, wird die Funktion neu bewertet.

Funktionen werden weitergegeben und mit einem std::function Wrapper gespeichert, so dass ich nicht mit Raw Funktionszeiger (yuck) arbeiten muss.

#include <iostream> 
#include <functional> 
#include <utility> 
#include <tuple> 

template <typename T> 
class LazyEvaluation {}; 

template <typename ReturnType, typename... Params> 
class LazyEvaluation<ReturnType(Params...)> { 
private: 
    std::function<ReturnType(Params...)> func_; 
    ReturnType result; 
    std::tuple<Params...> oldParams; // Contains the previous arguments. 
public: 
    explicit LazyEvaluation(std::function<ReturnType(Params...)> func) 
     : func_(std::move(func)) {} 
    template <typename... Args> 
    ReturnType operator() (Args&&... args) { 
     auto newParams = std::make_tuple(std::forward<Args>(args)...); 

     // Check if new arguments. 
     if (newParams != oldParams) { 
      result = func_(std::forward<Args>(args)...); 
      oldParams = newParams; 
      std::cout << "Function evaluated" << std::endl; 
     } 

     std::cout << "Returned result" << std::endl; 
     return result; 
    } 
}; 

int main() { 
    auto f = [] (int a, int b) { 
     return a + b; 
    }; 

    // Specify function type as template parameter. 
    // E.g. ReturnType(Param1Type, Param2Type, ..., ParamNType) 
    LazyEvaluation<int(int, int)> ld(f); 

    std::cout << ld(1, 2) << std::endl; 
    std::cout << ld(1, 2) << std::endl; 
    std::cout << ld(3, 4) << std::endl; 
} 

Ausgang:

Function evaluated 
Returned result 
3 
Returned result 
3 
Function evaluated 
Returned result 
7 
0

die Standardmaschinen zur Bildung variadische Index Packs Gegeben:

template <std::size_t... I> struct index_sequence {}; 
template <std::size_t N, std::size_t... I> 
struct make_index_sequence : public make_index_sequence<N-1, N-1, I...> {}; 
template <std::size_t... I> 
struct make_index_sequence<0, I...> : public index_sequence<I...> {}; 

und Funktionen aufrufen mit entpackten Tupel Argumente:

template <typename Function, typename... Types, std::size_t... I> 
auto apply_(Function&& f, const std::tuple<Types...>& t, index_sequence<I...>) 
    -> decltype(std::forward<Function>(f)(std::get<I>(t)...)) { 
    return std::forward<Function>(f)(std::get<I>(t)...); 
} 

template <typename Function, typename... Types> 
auto apply(Function&& f, const std::tuple<Types...>& t) 
    -> decltype(apply_(f, t, make_index_sequence<sizeof...(Types)>())) { 
    return apply_(f, t, make_index_sequence<sizeof...(Types)>()); 
} 

Diese ist ziemlich str aightforward:

template<typename Function, typename... Params> 
class LazyEvaluation { 
private: 
    typedef decltype(std::declval<Function>()(std::declval<Params>()...)) result_type; 
    // Function to be invoked later 
    Function f; 
    // Params for function f 
    std::tuple<Params...> storedParams; 
    mutable bool evaluated; 
    union { 
    std::aligned_storage<sizeof(result_type)> space; 
    mutable result_type result; 
    }; 

    // Method which can be called later to evaluate stored function with stored arguments 
    void evaluate() const { 
    // if not evaluated then evaluate 
    if (! evaluated) { 
     new (&result) result_type{apply(f, storedParams)}; 
     evaluated = true; 
    } 
    } 

public: 
    // Constructor remembers function pointer and parameters 
    LazyEvaluation(Function f, Params... params) 
    : f(std::move(f)), 
     storedParams(std::move(params)...), 
     evaluated(false) 
    {} 
    ~LazyEvaluation() { 
    if (evaluated) 
     result.~result_type(); 
    } 

    operator result_type&() { 
    evaluate(); 
    return result; 
    } 

    operator const result_type&() const { 
    evaluate(); 
    return result; 
    } 
}; 

template <typename Function, typename... Params> 
LazyEvaluation<Function, Params...> 
make_lazy(Function&& f, Params&&... params) { 
    return {std::forward<Function>(f), std::forward<Params>(params)...}; 
} 

ich eine Vereinigung und Platzierung verwendet haben new das Ergebnis der Auswertung zu speichern, damit sie müssen nicht ein default-konstruierbaren Typ sein, und einige mutable Tricks, so dass ein const LazyEvaluator umgewandelt werden kann sowie eine nicht konstante Instanz.