Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save variable number of arguments using variadic template arguments?

I would like to create template class which could store function pointer and arguments for a this function so the function can be later invoked with this arguments.

I would like to write this universally and not to depend on argument types or number.

Here is a scatch of the idea with the use of variadic templates of 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;
     }
}

I would like to have at least the public interface of this class type safe if it is possible. Although getting this work at least somehow is more important.

I've managed to save the variable number of arguments somehow. But I wasn't able to pass them to the function f. I will write it to answers, but I would like you to think about your own solutions before you see my ugly not working attempt.

I am tring to compile the code above with Microsoft Visual C++ Compiler Nov 2012 CTP (v120_CTP_Nov2012), but it would be best if a compiler independent solution would exist.

Thank you

like image 571
John Bumper Avatar asked Aug 01 '13 13:08

John Bumper


2 Answers

Here is how I tried to solve it:

The parametr pack can be recursivle expanded and each parametr saved. Function store is supposed to do it. It uses one (two times 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...);
}

Function store takes a pointer to memory where the varialbe number of arguments is supposed to be saved.

The pointer can point to some dynamicly allocated memory or beter to the structure which size is equal to sizeof...(Params). Such 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 am not sure what the standart says or how the other compilers than the microsoft one compile it. But using my compiler the sizeof(allocatorStruct) is equal to N for any N which is greater or equal to 1.

Hence allocatorStruct<sizeof...(Params)> has the same size as Params.

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

And now the main idea:

When saving the function we can cast it to: T (*)(allocatorStruct<sizeof...(Params)>). When saving the arguments for the function we can save them to struct of the type allocatorStruct<sizeof...(Params)>.

The size of the arguments is the same. Although the function pointer lies about the type of the function the function pointed to will get its data correctly.

At least I hoped. Depending on the calling convention I expected that the passed arguments can be reordered or wrong because of the difference between left to right saving arguments and right to left passing. But it wasn't the case. Using __cdecl calling convention only first argument was passed and the other was lost. With other calling conventions the program stoped working.

I didn't spend much time debugging it and looking to data in memory(on stack). Is it at least right way to go?

like image 185
John Bumper Avatar answered Oct 24 '22 01:10

John Bumper


Simply use a 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 the function once (lazily), using variadic templates, you could do something like in the following code.

I also made the class as such that you don't have to create a new instance every time the parameters change. I use a std::tuple to store the given arguments, and compare against previously given arguments. If the arguments differ, then the function will be reevaluated.

Functions are passed around and stored using a std::function wrapper so I don't have 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.
    // 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;
}

Output:

Function evaluated
Returned result
3
Returned result
3
Function evaluated
Returned result
7
like image 25
12 revs Avatar answered Oct 24 '22 01:10

12 revs