Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm trying to write a multiple functions memoizator, I talked about it here. The main problem is to create a container containing different and heterogenous functions.

I found a working solution, but I don't know if it's safe, if there are limitations, if it's somehow inefficient or if there is a more elegant solution.

This is my solution:

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;
    }
}
share|improve this question
    
I mean....3 up vote, and nobody answering/comminting? – user6321 Apr 22 at 7:20

it looks straightforward implementation of boost::any. but i can't understand what is memoize actually do?. since you can pass function arguments of addFunction directly into multiCache like so:

auto result = multiCache.insert({ name, func });

also, using string as key in std::map is considered an error-proven, since the return type is always same as the arguments in std::function. it is much easier if you used std::type_index which is defined in <type_index> header file. and the insertion will be like this:

auto result = multiCache.insert({ typeid(T), func });

also, in callFunction it always be preferred to take rvalue if you going to pass template arguments in the function parameters also, for expending pack arguments you need std::forward<Args>(args)... ofcourse.

so, the code will be some thing like this:

#include <functional>
#include <typeindex>
#include <string>
#include <tuple>
#include <map>
#include <cassert>
#include <iostream>
#include <boost/any.hpp>

class MultiMemoizator
{
public:

    template <typename F>
    void addFunction(F f)
    {
        auto result = multiCache.insert({ typeid(typename F::result_type), f });
        if (!result.second)
        {
            std::cout << "ERROR: key "
                + std::string(typeid(typename F::result_type).name())
                + " was already inserted\n";
        }
    }

    template <typename... Ts>
    typename std::tuple_element<0, std::tuple<Ts...> >::type // or decltype(auto) 
        callFunction(Ts&&... ts)
    {
        using type = typename std::tuple_element<0, std::tuple<Ts...> >::type;
        auto it = multiCache.find(typeid(type));
        assert(it != multiCache.end());
        auto&& fn = boost::any_cast<const std::function<type(Ts...)>&>(it->second);
        return fn(std::forward<Ts>(ts)...);
    }

private:
    std::map<std::type_index, boost::any> multiCache;
};

int main()
{
    std::function<int(int)> intFun = [](auto i) {return ++i; };
    std::function<std::string(std::string)> stringFun = [](const auto& s)
    {
        return "Hello" + s;
    };

    MultiMemoizator mem;
    mem.addFunction(intFun);
    mem.addFunction(stringFun);
    try
    {
        std::cout << mem.callFunction(1) << std::endl;
        std::cout << mem.callFunction(std::string(" World!")) << std::endl;
    }
    catch (boost::bad_any_cast& e)
    {
        std::cout << "Bad function calling: " << e.what() << std::endl;
        return 1;
    }
}
share|improve this answer
    
Ok, this is a little bit advanced code for me. What happens if I insert two function with the same signature? typeid(T) would be the same for both functions and so I could not insert the second one! But maybe I'm wrong, I never used it before. And in second place, could you rephrase the point about the reference passing please? – user6321 Apr 22 at 23:56
    
no, not allow for inserting two function with same signature, in the example i used the return type as key but if you need to store multiple functions with same signatures then you need to use a unique key for each of them, like enum. and for reference passing is simply to avoid un-necessary copy for the objects – MORTAL 2 days ago
    
You're suggesting to use an enum instead of typeid(T) so at compile time each function is uniquely identified. However,I would like to expand this solution to a multi user experience, where an user can write it's multicache on file, and another user can re-use it in its program. Since the other user could define another enum for the same functions, there could be no match. So what about if the original user write SHA code for each function (which is probably unique), write the map (nameFunction,shaFunction) on file and expose it to the other user and the multiCache is(shaFunction, anyFunction? – user6321 2 days ago
    
i have not used SHA code before, but it worth to try. the main point of key is has to be unique if SHA code guaranteed the uniqueness that is will be good – MORTAL 2 days ago
    
Check out this question that I opened: stackoverflow.com/questions/36809025/… – user6321 2 days ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.