3
votes

I am trying to write a simple template that I can use for memoization with functions taking a single argument:

#include <map>          

template <typename F,typename OUT,typename IN> 
OUT memoization(IN in){
    static std::map<IN,OUT> memo;
    static typename std::map<IN,OUT>::iterator found = memo.find(in);
    if (found != memo.end()) { return found->second; }  
    OUT res = F(in);
    memo(in) = res;
    return res;
}

double test(double x) { return x*x; }

int main(){
    for (int i=0;i<5;i++){
        memoization<test,double,double>(i*0.5);
    }
}

But i get the error:

error: no matching function for call to 'memoization(double)'

note: candidate is:

note: template OUT memoization(IN)

note: template argument deduction/substitution failed:

Why does this fail to compile?

Actually I dont understand why template argument deduction/substitution is taking place at all when I specify all the template parameters.

I am using gcc version 4.7.2 (no C++11 enabled)

PS: the template has many more errors than I first realized, but I leave it as is...

3
test is not a type. decltype(test) is. - MadScientist

3 Answers

5
votes

Your function template takes three type arguments:

template <typename F,typename OUT,typename IN> 
OUT memoization(IN in) { ... }

You're passing in test for F. test isn't a type, it's a value. Also, the expression F(in) in your function template is wrong for the same reason.


This approach in general is pretty flawed as it seems pretty backwards from what actually is going on. Namely, it's the function that's being memoized, not a value. Also requiring the function value at compile time is pretty limiting.

A better approach is to treat memoization as a decorator. That is:

template <class F>
Memoized<F> memoize(F f) {
    return {f};
}

such that:

auto memo_test = memoize(test);
memo_test(0); // performs computation
memo_test(0); // doesn't perform computation
memo_test(0); // ditto

I leave the implementation of Memoized<T> as an exercise.

0
votes

Why does template argument deduction/substitution fail here?

a. Because there are 3 template arguments and only one actual argument, so two of them are undeduce-able (is that a word?).

b. There is a syntax error. Template argument F is a type, not a callable object.

If this has to work in a pre-c++11 environment, boost's result_of can help:

#include <map>         
#include <boost/utility/result_of.hpp>

//
// now that template arguments are all used when collecting function
// arguments, the types F and IN can be deduced.
//    
template <typename F,typename IN> 
typename boost::result_of<F(IN)>::type memoization(F f, IN in)
{
  typedef typename boost::result_of<F(IN)>::type OUT;
    static std::map<IN,OUT> memo;
    static typename std::map<IN,OUT>::iterator found = memo.find(in);
    if (found != memo.end()) { return found->second; }  
    OUT res = f(in);
    memo[in] = res;
    return res;
}

double test(double x) { return x*x; }

int main(){
    for (int i=0;i<5;i++){
        memoization(test, i*0.5);
    }
}
0
votes

The answer already got a satisfactory answers, however I was curious if I could get it working with pre C++11. Actually it is possible to pass a function pointer as template parameter, one just has to specify this on the template parameter instead of letting it expect a type parameter:

#include <iostream>
#include <map>
using namespace std;
 
template <class T, class R, R (*Func)(T)>
R memoized(T in) {
    static std::map<T,R> memo;
    typename std::map<T,R>::iterator found = memo.find(in);
    if (found != memo.end()) { return found->second; }  
    std::cout << "not found" << std::endl;
    R res = Func(in);
    memo[in] = res;
    return res;
}
 
double test(double x){return x*x;}
double test2(double x){return x;}
 
int main() {
    std::cout << memoized<double,double,test>(1) << std::endl;
    std::cout << memoized<double,double,test>(1) << std::endl;
    std::cout << memoized<double,double,test>(1) << std::endl;
    std::cout << std::endl;
    std::cout << memoized<double,double,test2>(1) << std::endl;
    std::cout << memoized<double,double,test2>(1) << std::endl;
    std::cout << memoized<double,double,test2>(1) << std::endl;
 
    return 0;
}

output:

not found
1
1
1

not found
1
1
1

Still not sure if this is a good approach, but it seems to work.