不同签名函数的容器
Containers for different signature functions
我正在尝试用 C++ 编写一个框架,用户可以在其中指示其程序中的一组函数,他想在其中应用 memoization 策略。
所以假设我们的程序 f1...f5
中有 5 个函数,如果我们已经用相同的输入调用它们。 请注意,每个函数可以有不同的 return 和参数类型。
我找到了问题的 this 解决方案,但您只能使用 double
和 int
。
我的解决方案
好的,我为我的问题写了这个解决方案,但我不知道它是否高效、类型安全或者是否可以用更优雅的方式编写。
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 <class RetType, class ArgType>
class AbstractFunction {
//etc.
}
让 AbstractFunction 接受一个指向函数 f1-f5 的函数指针,每个函数都有不同的模板特化。然后,您可以拥有一个通用 run_memoized() 函数,作为 AbstractFunction 的成员函数或将 AbstractFunction 作为参数并在运行时维护备忘录的模板函数。
最难的部分是函数 f1-f5 是否有多个参数,在这种情况下,您需要使用 arglists 作为模板参数做一些时髦的事情,但我认为 C++14 有一些特性可能使这成为可能。另一种方法是重写 f1-f5,以便它们都将单个结构作为参数而不是多个参数。
编辑:看到你的问题 1,你 运行 遇到的问题是你想要一个数据结构,其值是记忆函数,每个函数都可以有不同的参数。
我个人会通过使数据结构使用 void* 来表示各个记忆函数来解决这个问题,然后在 callFunction() 方法中使用从 void* 转换为模板化 MemoizedFunction 类型的不安全类型需要(您可能需要使用 "new" 运算符分配 MemoizedFunctions,以便您可以将它们与 void*s 相互转换。)
如果这里缺乏类型安全让您感到厌烦,这对您有好处,在这种情况下,为 f1-f5 中的每一个制作手写辅助方法并让 callFunction() 分派其中之一可能是一个合理的选择这些函数基于输入字符串。这将使您可以使用编译时类型检查。
编辑 #2:如果您打算使用这种方法,则需要稍微更改 callFunction() 的 API,以便 callFunction 具有与 return 和参数类型匹配的模板参数函数,例如:
int result = callFunction<int, arglist(double, float)>("double_and_float_to_int", 3.5, 4);
并且如果此 API 的用户在使用 callFunction 时曾输入参数类型或 return 输入不正确...为他们的灵魂祈祷,因为事情会以非常丑陋的方式爆炸。
编辑 #3:您可以在某种程度上使用 std::type_info 进行运行时所需的类型检查,并将参数类型的 typeid() 和 return 类型存储在 MemoizedFunction 中,以便你可以在调用之前检查 callFunction() 中的模板参数是否正确——这样你就可以防止上面的爆炸。但这会在您每次调用该函数时增加一些开销(您可以将其包装在 IF_DEBUG_MODE 宏中以仅在测试期间而不是在生产中添加此开销。)
这样的事情怎么样:
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
您可以使用带有 void someFunction(void *r, ...)
签名的函数向量,其中 r
是指向结果的指针,...
是可变参数列表。警告:解包参数列表真的很不方便,看起来更像是 hack。
很难猜测您打算如何使用这些功能,有或没有记忆,但对于各种容器 function<>
s 方面,您只需要一个通用基础 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);
}
问题在于如何使其类型安全。看这段代码:
MultiMemoizator mm;
std::string name = "identity";
mm.addFunction(name, identity);
auto result = mm.callFunction(name, 1);
最后一行是否正确? callFunction
是否具有正确类型的正确数量的参数? return 类型是什么?
编译器无法知道:它无法理解 name
是 "identity"
,即使理解,也无法将其与函数类型相关联。而且这不是 C++ 特有的,任何静态类型的语言都会有同样的问题。
一种解决方案(基本上是 Tony D 的回答中给出的解决方案)是在调用函数时告诉编译器函数签名。如果你说错了,就会发生运行时错误。这可能看起来像这样(您只需要明确指定 return 类型,因为参数的数量和类型是推断出来的):
auto result = mm.callFunction<int>(name, 1);
但这并不优雅且容易出错。
根据您的具体要求,使用 "smart" 键而不是字符串可能会更好:键的类型中嵌入了函数签名,因此您不必担心指定它正确。这可能看起来像:
Key<int(int)> identityKey;
mm.addFunction(identityKey, identity);
auto result = mm.callFunction(identityKey, 1);
这样,类型在编译时被检查(addFunction
和 callFunction
),这应该给你你想要的。
我还没有在 C++ 中实际实现它,但我看不出有任何理由说明它应该很难或不可能。尤其是 doing something very similar in C# is simple.
我正在尝试用 C++ 编写一个框架,用户可以在其中指示其程序中的一组函数,他想在其中应用 memoization 策略。
所以假设我们的程序 f1...f5
中有 5 个函数,如果我们已经用相同的输入调用它们。 请注意,每个函数可以有不同的 return 和参数类型。
我找到了问题的 this 解决方案,但您只能使用 double
和 int
。
我的解决方案
好的,我为我的问题写了这个解决方案,但我不知道它是否高效、类型安全或者是否可以用更优雅的方式编写。
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 <class RetType, class ArgType>
class AbstractFunction {
//etc.
}
让 AbstractFunction 接受一个指向函数 f1-f5 的函数指针,每个函数都有不同的模板特化。然后,您可以拥有一个通用 run_memoized() 函数,作为 AbstractFunction 的成员函数或将 AbstractFunction 作为参数并在运行时维护备忘录的模板函数。
最难的部分是函数 f1-f5 是否有多个参数,在这种情况下,您需要使用 arglists 作为模板参数做一些时髦的事情,但我认为 C++14 有一些特性可能使这成为可能。另一种方法是重写 f1-f5,以便它们都将单个结构作为参数而不是多个参数。
编辑:看到你的问题 1,你 运行 遇到的问题是你想要一个数据结构,其值是记忆函数,每个函数都可以有不同的参数。
我个人会通过使数据结构使用 void* 来表示各个记忆函数来解决这个问题,然后在 callFunction() 方法中使用从 void* 转换为模板化 MemoizedFunction 类型的不安全类型需要(您可能需要使用 "new" 运算符分配 MemoizedFunctions,以便您可以将它们与 void*s 相互转换。)
如果这里缺乏类型安全让您感到厌烦,这对您有好处,在这种情况下,为 f1-f5 中的每一个制作手写辅助方法并让 callFunction() 分派其中之一可能是一个合理的选择这些函数基于输入字符串。这将使您可以使用编译时类型检查。
编辑 #2:如果您打算使用这种方法,则需要稍微更改 callFunction() 的 API,以便 callFunction 具有与 return 和参数类型匹配的模板参数函数,例如:
int result = callFunction<int, arglist(double, float)>("double_and_float_to_int", 3.5, 4);
并且如果此 API 的用户在使用 callFunction 时曾输入参数类型或 return 输入不正确...为他们的灵魂祈祷,因为事情会以非常丑陋的方式爆炸。
编辑 #3:您可以在某种程度上使用 std::type_info 进行运行时所需的类型检查,并将参数类型的 typeid() 和 return 类型存储在 MemoizedFunction 中,以便你可以在调用之前检查 callFunction() 中的模板参数是否正确——这样你就可以防止上面的爆炸。但这会在您每次调用该函数时增加一些开销(您可以将其包装在 IF_DEBUG_MODE 宏中以仅在测试期间而不是在生产中添加此开销。)
这样的事情怎么样:
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
您可以使用带有 void someFunction(void *r, ...)
签名的函数向量,其中 r
是指向结果的指针,...
是可变参数列表。警告:解包参数列表真的很不方便,看起来更像是 hack。
很难猜测您打算如何使用这些功能,有或没有记忆,但对于各种容器 function<>
s 方面,您只需要一个通用基础 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);
}
问题在于如何使其类型安全。看这段代码:
MultiMemoizator mm;
std::string name = "identity";
mm.addFunction(name, identity);
auto result = mm.callFunction(name, 1);
最后一行是否正确? callFunction
是否具有正确类型的正确数量的参数? return 类型是什么?
编译器无法知道:它无法理解 name
是 "identity"
,即使理解,也无法将其与函数类型相关联。而且这不是 C++ 特有的,任何静态类型的语言都会有同样的问题。
一种解决方案(基本上是 Tony D 的回答中给出的解决方案)是在调用函数时告诉编译器函数签名。如果你说错了,就会发生运行时错误。这可能看起来像这样(您只需要明确指定 return 类型,因为参数的数量和类型是推断出来的):
auto result = mm.callFunction<int>(name, 1);
但这并不优雅且容易出错。
根据您的具体要求,使用 "smart" 键而不是字符串可能会更好:键的类型中嵌入了函数签名,因此您不必担心指定它正确。这可能看起来像:
Key<int(int)> identityKey;
mm.addFunction(identityKey, identity);
auto result = mm.callFunction(identityKey, 1);
这样,类型在编译时被检查(addFunction
和 callFunction
),这应该给你你想要的。
我还没有在 C++ 中实际实现它,但我看不出有任何理由说明它应该很难或不可能。尤其是 doing something very similar in C# is simple.