用于不同签名函数的容器

Containers for different signature functions

本文关键字:函数 用于      更新时间:2023-10-16

我正在尝试在C++中编程一个框架,在这个框架中,用户可以在程序中指示一组函数,他想在其中应用记忆策略。

因此,让我们假设我们的程序f1...f5中有5个函数,并且如果我们已经用相同的输入调用了函数f1f3,我们希望避免对它们进行(昂贵的)重新计算请注意,每个函数可以有不同的返回类型和参数类型

我找到了这个问题的解决方案,但您只能使用doubleint

我的解决方案

好的,我为我的问题写了这个解决方案,但我不知道它是否有效、类型安全,或者可以用更优雅的方式写。

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

这是一个可能的主要:

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

这样的东西怎么样:

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

用法如下:

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

很难猜测您计划如何使用这些函数,无论是否使用内存,但对于各种function<>方面的容器,您只需要一个通用基类:

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

这方面的问题是如何使其类型安全。看看这个代码:

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

最后一行正确吗?callFunction是否具有正确数量和正确类型的参数?返回类型是什么?

编译器无法知道这一点:它无法理解name"identity",即使它理解了,也无法将其与函数类型相关联。这并不是C++特有的,任何静态类型的语言都会有同样的问题。

一种解决方案(基本上就是Tony D的答案中给出的解决方案)是在调用函数时告诉编译器函数签名。如果你说错了,就会发生运行时错误。这可能看起来像这样(您只需要显式指定返回类型,因为参数的数量和类型是推断出来的):

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

但这是不雅和容易出错的。

根据您的具体要求,可能更有效的方法是使用"智能"键,而不是字符串:键的类型中嵌入了函数签名,因此您不必担心是否正确指定它。这可能看起来像:

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

通过这种方式,在编译时检查类型(addFunctioncallFunction都是),这应该会提供您想要的内容。

我实际上还没有在C++中实现这一点,但我看不出有任何理由认为这很难或不可能。特别是因为在C#中做一些非常相似的事情很简单。

您可以使用具有签名的函数向量,如void someFunction(void *r, ...),其中r是指向结果的指针,...是可变参数列表。警告:打开参数列表真的很不方便,看起来更像是一个黑客。

乍一看,定义一个类型,该类型的模板参数对每个函数都不同,例如:

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

让AbstractFunction取一个指向函数f1-f5的函数指针,每个函数的模板专门化不同。然后,您可以有一个通用的run_memoized()函数,作为AbstractFunction的成员函数,或者是一个以AbstractFunction为参数并在运行时维护备忘录的模板函数。

最困难的部分是如果函数f1-f5有多个参数,在这种情况下,你需要用arglist作为模板参数来做一些奇怪的事情,但我认为C++14有一些功能可以实现这一点。另一种选择是重写f1-f5,使它们都将单个结构作为参数,而不是多个参数。

编辑:看过你的问题1后,你遇到的问题是你想要一个数据结构,它的值是记忆函数,每个函数都可能有不同的参数。

I、 就个人而言,只需让数据结构使用void*来表示各个已存储的函数,然后在callFunction()方法中使用从void*转换为所需模板化MemoizedFunction类型的不安全类型即可解决此问题(您可能需要使用"new"运算符分配MemoizedFunctions,以便将它们转换为void*s和从void*s.)

如果这里缺乏类型安全性让你很恼火,这对你来说很好,那么在这种情况下,只为f1-f5中的每一个创建手工编写的辅助方法并让callFunction()根据输入字符串调度其中一个函数可能是一个合理的选择。这将允许您使用编译时类型检查。

编辑#2:如果您要使用这种方法,您需要稍微更改callFunction()的API,以便callFunction具有与函数的返回和参数类型匹配的模板参数,例如:

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

如果此API的用户在使用callFunction时错误地键入了参数类型或返回类型。。。为他们的灵魂祈祷,因为事情会以非常丑陋的方式爆发。

EDIT#3:在某种程度上,您可以在运行时使用std::type_info进行所需的类型检查,并将参数类型和返回类型的typeid()存储在MemoizedFunction中,这样您就可以在调用之前检查callFunction()中的模板参数是否正确,这样就可以防止上述爆炸。但每次调用函数时,这都会增加一点开销(可以将其封装在IF_DEBUG_MODE宏中,只在测试期间而不是在生产中添加此开销。)