3

I'm trying to programming in C++ a framework where the user can indicates a set of functions inside its program where he wants to apply a memoization strategy.

So let's suppose that we have 5 functions in our program f1...f5 and we want to avoid the (expensive) re-computation for the functions f1 and f3 if we already called them with the same input. Notice that each function can have different return and argument types.

I found this solution for the problem, but you can use only double and int.

MY SOLUTION

Ok I wrote this solution for my problem, but I don't know if it's efficient, typesafe or can be written in any more elegant way.

template <typename ReturnType, typename... Args>
function<ReturnType(Args...)> memoize(function<ReturnType(Args...)> func)
{
    return ([=](Args... args) mutable {
        static map<tuple<Args...>, ReturnType> cache;
        tuple<Args...> t(args...);
        auto result = cache.insert(make_pair(t, ReturnType{}));
        if (result.second) {
            // insertion succeeded so the value wasn't cached already
            result.first->second = func(args...);
        }
        return result.first->second;
    });
}

struct MultiMemoizator
{
    map<string, boost::any> multiCache;
    template <typename ReturnType, typename... Args>
    void addFunction(string name, function < ReturnType(Args...)> func) {
        function < ReturnType(Args...)> cachedFunc = memoize(func);
        boost::any anyCachedFunc = cachedFunc;
        auto result = multiCache.insert(pair<string, boost::any>(name,anyCachedFunc));
        if (!result.second)
            cout << "ERROR: key " + name + " was already inserted" << endl;
    }
    template <typename ReturnType, typename... Args>
    ReturnType callFunction(string name, Args... args) {
        auto it = multiCache.find(name);
        if (it == multiCache.end())
            throw KeyNotFound(name);
        boost::any anyCachedFunc = it->second;
        function < ReturnType(Args...)> cachedFunc = boost::any_cast<function<ReturnType(Args...)>> (anyCachedFunc);
        return cachedFunc(args...);
    }
};

And this is a possible main:

int main()
{
    function<int(int)> intFun = [](int i) {return ++i; };
    function<string(string)> stringFun = [](string s) {
        return "Hello "+s;
    };
    MultiMemoizator mem;
    mem.addFunction("intFun",intFun);
    mem.addFunction("stringFun", stringFun);
    try
    {
        cout << mem.callFunction<int, int>("intFun", 1)<<endl;//print 2
        cout << mem.callFunction<string, string>("stringFun", " World!") << endl;//print Hello World!
        cout << mem.callFunction<string, string>("TrumpIsADickHead", " World!") << endl;//KeyNotFound thrown
    }
    catch (boost::bad_any_cast e)
    {
        cout << "Bad function calling: "<<e.what()<<endl;
        return 1;
    }
    catch (KeyNotFound e) 
    {
        cout << e.what()<<endl;
        return 1;
    }
}
Community
  • 1
  • 1
justHelloWorld
  • 6,478
  • 8
  • 58
  • 138
  • Why do you need a container of these functions? Why not simply write a template class which the outer program has to instantiate and hold onto? It would then be fairly simple for that class to map argument tuples to result values. I'm not sure why you'd want a heterogeneous container of functions...it depends what the external API is supposed to be like. – John Zwinck Apr 21 '16 at 03:39
  • Updated with a first solution – justHelloWorld Apr 21 '16 at 03:48
  • @JohnZwinck I'm not sure that I understood your solution, please write an answer if you have one. – justHelloWorld Apr 21 '16 at 03:50
  • 1
    assuming you have list of functions. How are you going to use them? Argument list is still specified at compile-time – Andrei R. Apr 21 '16 at 03:54
  • Question updated with how the API should look like with 2 problems. – justHelloWorld Apr 21 '16 at 04:34
  • Thanks for adding the examples. It seems that mostly what you're after is a container that lets you store functions with heterogeneous signatures and invoke them using their names stored in strings. I have to say, this makes me wonder why you're targeting C++, when you're doing dynamic binding anyway. There is clearly no way that the compiler will be able to know the types of the arguments required for a given function when it is specified by name in a string. What you're asking for seems to be like a first step toward Boost.Python. – John Zwinck Apr 21 '16 at 06:59
  • @JohnZwinck thanks for your answer. I think the same, and in fact I opened this question: http://stackoverflow.com/questions/36760993/programming-language-with-containers-for-different-functions Could you suggest me a different language where it's easy to implement what I'm looking for? – justHelloWorld Apr 21 '16 at 07:03
  • @justHelloWorld: Python would be easier for example. But if you're implementing a programming framework then you need to be tied to some particular language I guess. I am still 100% unclear on why you want to look up functions by string name but call them with argument lists whose types and arity are known at compile time. Saying you want to make a "framework" doesn't really help illuminate whatever the real problem is that you're having. – John Zwinck Apr 21 '16 at 07:06
  • @JohnZwinck I'm still considering which language use for my framework. Sure, C++ would be better because I'm more confident and has in general better performance, but it's too strict in these cases. The lookup through string is just an example, any more elegant solution is well accepted. Could you please tell me how to do it in python (which I don't know)? – justHelloWorld Apr 21 '16 at 07:12
  • Updated with possible solution: is it safe? efficient? there exists a more elegant solution? – justHelloWorld Apr 22 '16 at 09:09
  • Check out this question that I opened: http://stackoverflow.com/questions/36809025/share-a-multi-type-map-between-users?noredirect=1#comment61191133_36809025 – justHelloWorld Apr 23 '16 at 13:17

5 Answers5

1

How about something like this:

template <typename result_t, typename... args_t>
class Memoizer
{
public:
    typedef result_t (*function_t)(args_t...);
    Memoizer(function_t func) : m_func(func) {}

    result_t operator() (args_t... args)
    {
        auto args_tuple = make_tuple(args...);

        auto it = m_results.find(args_tuple);
        if (it != m_results.end())
            return it->second;

        result_t result = m_func(args...);
        m_results.insert(make_pair(args_tuple, result));
        return result;
    }

protected:
    function_t m_func;
    map<tuple<args_t...>, result_t> m_results;
};

Usage is like this:

// could create make_memoizer like make_tuple to eliminate the template arguments
Memoizer<double, double> memo(fabs);
cout << memo(-123.456);
cout << memo(-123.456); // not recomputed
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

It's pretty hard to guess at how you're planning to use the functions, with or without memoisation, but for the container-of-various-function<>s aspect you just need a common base class:

#include <iostream>
#include <vector>
#include <functional>

struct Any_Function
{
    virtual ~Any_Function() {}
};

template <typename Ret, typename... Args>
struct Function : Any_Function, std::function<Ret(Args...)>
{
    template <typename T>
    Function(T& f)
      : std::function<Ret(Args...)>(f)
    { }
};

int main()
{
    std::vector<Any_Function*> fun_vect;
    auto* p = new Function<int, double, double, int> { [](double i, double j, int z) {
        return int(i + j + z);
    } };
    fun_vect.push_back(p);
}
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
1

The problem with this is how to make it type-safe. Look at this code:

MultiMemoizator mm;
std::string name = "identity";
mm.addFunction(name, identity);
auto result = mm.callFunction(name, 1);

Is the last line correct? Does callFunction have the right number of parameters with the right types? And what is the return type?

The compiler has no way to know that: it has no way of understanding that name is "identity" and even if it did, no way to associate that with the type of the function. And this is not specific to C++, any statically-typed language is going to have the same problem.

One solution (which is basically the one given in Tony D's answer) is to tell the compiler the function signature when you call the function. And if you say it wrong, a runtime error occurs. That could look something like this (you only need to explicitly specify the return type, since the number and type of parameters is inferred):

auto result = mm.callFunction<int>(name, 1);

But this is inelegant and error-prone.

Depending on your exact requirements, what might work better is to use "smart" keys, instead of strings: the key has the function signature embedded in its type, so you don't have to worry about specifying it correctly. That could look something like:

Key<int(int)> identityKey;
mm.addFunction(identityKey, identity);
auto result = mm.callFunction(identityKey, 1);

This way, the types are checked at compile time (both for addFunction and callFunction), which should give you exactly what you want.

I haven't actually implemented this in C++, but I don't see any reason why it should be hard or impossible. Especially since doing something very similar in C# is simple.

Community
  • 1
  • 1
svick
  • 236,525
  • 50
  • 385
  • 514
  • But with this solution, once you add `Key` all the keys must be of the same time, so we cannot add `Key` for example along the previous key. Am I wrong? – justHelloWorld Apr 22 '16 at 02:10
  • Updated with possible solution: is it safe? efficient? there exists a more elegant solution? – justHelloWorld Apr 22 '16 at 09:10
  • @justHelloWorld No, they don't have to be all the same type. It would be pretty similar to the solution you showed, except safer. – svick Apr 22 '16 at 10:20
  • Thanks for your answer. How my sokution could no be safe? – justHelloWorld Apr 22 '16 at 12:09
  • @justHelloWorld Because when you make a mistake when specifying the types, you will only find out at runtime. – svick Apr 22 '16 at 14:00
  • I get your point, I think you're right. But what happens if I try to insert different functions with same signature? I would try to insert to Key, and so the second one would not be added, am I right? – justHelloWorld Apr 23 '16 at 06:36
  • @justHelloWorld No, each new `Key` you create will contain some unique ID, which is used as the actual key in the hash table. That way, you can have multiple functions with the same signature. – svick Apr 23 '16 at 13:06
  • Check out this question that I opened: http://stackoverflow.com/questions/36809025/share-a-multi-type-map-between-users?noredirect=1#comment61191133_36809025 – justHelloWorld Apr 23 '16 at 13:17
0

At first glance, how about defining a type that has template arguments that differ for each function, i.e.:

template <class RetType, class ArgType> 
class AbstractFunction {
    //etc.
}

have the AbstractFunction take a function pointer to the functions f1-f5 with template specializations different for each function. You can then have a generic run_memoized() function, either as a member function of AbstractFunction or a templated function that takes an AbstractFunction as an argument and maintains a memo as it runs it.

The hardest part will be if the functions f1-f5 have more than one argument, in which case you'll need to do some funky things with arglists as template parameters but I think C++14 has some features that might make this possible. An alternative is to rewrite f1-f5 so that they all take a single struct as an argument rather than multiple arguments.

EDIT: Having seen your problem 1, the problem you're running into is that you want to have a data structure whose values are memoized functions, each of which could have different arguments.

I, personally, would solve this just by making the data structure use void* to represent the individual memoized functions, and then in the callFunction() method use an unsafe type cast from void* to the templated MemoizedFunction type you need (you may need to allocate MemoizedFunctions with the "new" operator so that you can convert them to and from void*s.)

If the lack of type safety here irks you, good for you, in that case it may be a reasonable option just to make hand-written helper methods for each of f1-f5 and have callFunction() dispatch one of those functions based on the input string. This will let you use compile-time type checking.

EDIT #2: If you are going to use this approach, you need to change the API for callFunction() slightly so that callFunction has template args matching the return and argument types of the function, for example:

int result = callFunction<int, arglist(double, float)>("double_and_float_to_int", 3.5, 4);

and if the user of this API ever types the argument type or return types incorrectly when using callFunction... pray for their soul because things will explode in very ugly ways.

EDIT #3: You can to some extent do the type checking you need at runtime using std::type_info and storing the typeid() of the argument type and return type in your MemoizedFunction so that you can check whether the template arguments in callFunction() are correct before calling - so you can prevent the explosion above. But this will add a bit of overhead every time you call the function (you could wrap this in a IF_DEBUG_MODE macro to only add this overhead during testing and not in production.)

Ben Braun
  • 418
  • 4
  • 10
0

you can use vector of functions with signature like void someFunction(void *r, ...) where r is a pointer to result and ... is variadic argument list. Warning: unpacking argument list is really inconvenient and looks more like a hack.

Andrei R.
  • 2,374
  • 1
  • 13
  • 27
  • Question updated with how the API should look like with 2 problems. – justHelloWorld Apr 21 '16 at 04:35
  • @justHelloWorld, so make map of such functions and implement `callfunction` as `void *r callfunction(std::string name, ...) { return map[name](/*unpack parameters here*/); } – Andrei R. Apr 21 '16 at 06:38
  • Updated with possible solution: is it safe? efficient? there exists a more elegant solution? – justHelloWorld Apr 22 '16 at 09:10
  • @justHelloWorld, it is just as much safe as correctly you use it. And this is only way I know of specifying completely abstract functions. I can also suggest to pass/read function parameters as `std::tuple`s - to provide cleaner code and possibility to make templated call wrapper. – Andrei R. Apr 22 '16 at 09:39