How to save a variable number of arguments using the arguments of the variational pattern?

I would like to create a template class that could store a pointer and function arguments for this function, so that this function is subsequently called using these arguments.

I would like to write this universally and not depend on the types or number of arguments.

Here's a quick overview of ideas using C ++ 11 variable templates:

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

I would like to have at least an open interface of this class, if possible. Although getting this job is at least somehow more important.

I managed somehow to save a variable number of arguments. But I could not pass them to f. I will write it to the answers, but I would like you to think about your own decisions before you see my ugly non-working attempt.

I am trying to compile the code above using the Microsoft Visual C ++ Compiler Nov 2012 CTP (v120_CTP_Nov2012), but it would be better if there was a compiler independent solution.

thanks

+6
source share
3 answers

Here's how I tried to solve it:

The parametr package can be recursively extended and each parameter is saved. A functional store should do this. It uses one (double overloaded) helper function.

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

The function store takes a pointer to memory, where it is supposed to store the number of varialbe arguments.

A pointer can point to some dynamically allocated memory or a label to a structure whose size is equal to sizeof...(Params) . Such a structure, which has exactly any desiared size, can be constructed using template metaprogramming:

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

I'm not sure what the standard says or as compilers of other compilers than Microsoft compilation. But using my compiler, sizeof (allocatorStruct) is N for any N that is greater than or equal to 1.

Therefore, allocatorStruct<sizeof...(Params)> is the same size as Params.

Another way to create something the same size as Params is to use the char [sizeof...(Params)] . This has the disadvantage that the compiler only passes a pointer to this array when you try to pass such an array as an argument. Therefore, it is better to use allocatorStruct<sizeof...(Params)> .

And now the main idea:

When saving a function, we can overlay it on: T (*)(allocatorStruct<sizeof...(Params)>) . When saving arguments for a function, we can save them in a struct of type allocatorStruct<sizeof...(Params)> .

The size of the arguments is the same. Although the function pointer is about the type of function, the specified function will return its data.

At least I was hoping. Depending on the calling convention, I expected that the arguments passed might be reordered or incorrect due to the difference between the save arguments from left to right and going from right to left. But that was not so. With __cdecl, only the first argument is called, and the other is lost. With other calling conventions, the program has stopped working.

I did not spend much time debugging and searching for data in memory (on the stack). Is this at least the right way?

0
source

Just use lambda expression

 // 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. 

If you really want to create a class that only evaluates a function once (lazily) using variable templates, you can do something like the following code.

I also made the class so that you do not need to create a new instance every time you change the parameters. I use std::tuple to store the argument data and compare it with the previously given arguments. If the arguments are different, the function will be reevaluated.

Functions are passed and stored using the std::function shell, so I don’t need to work with raw function pointers (yuck).

 #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. // Eg 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; } 

Output:

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

Given the standard equipment for forming the packets of the variation index:

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

and call functions with unpacked arguments:

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

It is pretty simple:

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

I used the union and placement of new to keep the result of the evaluation so that it is not a standard default type, and some tricks are mutable , so that you can convert const LazyEvaluator as well as a non-constant instance.

0
source

Source: https://habr.com/ru/post/950822/


All Articles