有没有办法在 C++ 中创建组合函数编译时数组?
Is there a way to create an array of combined functions compile-time in c++?
我正在使用 C++ 开发 NES 模拟器,我认为 运行 操作码最有效的方法是调用函数数组中的函数指针,这些函数指针完全执行操作码的功能。
问题是每个操作码都有特定的操作和内存地址。在寻找解决方案时,我偶然发现了 lambda 表达式。这对于现代硬件上的 NES 模拟器来说绝对足够好。但是,我找不到解决方案,使数组中的每个函数都包含用于操作和寻址的机器代码,而无需定义 256 个单独的函数。
这就是我对结合 f 和 g 的类似函数的想法:
int addone(int x) {
return x + 1;
}
int multtwo(int x) {
return 2 * x;
}
something combine(function<int(int)> f, function <int(int)> g) {
/* code here */
}
/*
combine(addone, multtwo) creates a function h that has the same machine code as
int h(x) {
return 2 * x + 1;
}
*/
有什么想法吗?如果非要我猜的话,它应该与模板有关。谢谢!
您可以这样做,使用 lambda 捕获两个函数并将函数分配给变量:
#include <functional>
#include <iostream>
int addone(int x){
return x + 1;
}
int multtwo(int x){
return x * 2;
}
std::function<int(int)> combine(std::function<int(int)> f, std::function<int(int)> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<int(int)>(tmp);
}
int main(){
auto h = combine(std::function<int(int)>(addone), std::function<int(int)>(multtwo)); // (2 * x) + 1
std::cout << h(10); // Prints 21
}
如果你想让它统一功能,可以使用模板:
#include <functional>
#include <iostream>
int addone(int x){
return x + 1;
}
int multtwo(int x){
return x * 2;
}
template <typename Func>
std::function<Func> combine(std::function<Func> f, std::function<Func> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<Func>(tmp);
}
int main(){
auto h = combine<int(int)>(std::function<int(int)>(addone), std::function<int(int)>(multtwo));
std::cout << h(10) << "\n"; // Prints 21
}
您也不需要指定类型,因为编译器可以计算出来:
#include <functional>
#include <iostream>
int addone(int x){
return x + 1;
}
int multtwo(int x){
return x * 2;
}
template <typename func>
std::function<Func> combine(std::function<Func> f, std::function<Func> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<Func>(tmp);
}
int main(){
auto h = combine(std::function<int(int)>(addone), std::function<int(int)>(multtwo));
std::cout << h(10) << "\n"; // Still prints 21
}
如果您想自动创建函数,请使用 2pichar 的答案和 for 循环,但对于模拟器,您可能需要类似 opcode->int(*)(int)
的东西。这可以通过一些 tree-like 结构来完成:
std::map<char, naive_opcode> opcodes;
struct naive_opcode {
std::map<char, naive_opcode> next;
int(* opcode_func)(int);
};
您可以通过以下方式解决此问题:
char data;
buf >> data;
naive_opcode opcode = opcodes[data];
while(!opcode.opcode_func){
buf >> data;
opcode = opcode.next[data];
}
opcode.opcode_func(param);
这当然会忽略错误,并且不包括指令指针和 .text
段内存之类的东西,而是将其替换为 buf
缓冲区以用于说明目的(在现实生活中的示例中,我希望这会被 data=memory[ip]; ++ip;
取代)。然后可以将其与以下实现相结合:
#include <iostream>
int addone(int x){
return x + 1;
}
int multtwo(int x){
return x * 2;
}
template<int(* F)(int), int(* G)(int)>
int combined(int x){
return F(G(x));
}
int main(){
std::cout << combined<addone,multtwo>(10);
}
你基本上可以将 naive_opcode
的结束节点定义为 {{}, combined<addone,multtwo>}
.
不幸的是,正如我在评论中提到的,这可能无法自动完成。我侦察的你能做的最好的事情就是定义如下内容:
std::vector<std::pair<const char*, int(*)(int)>> raw_opcodes = {{"\x10\x13", addone}, ...};
然后将其解析为树状结构。作为一个简短的旁注:如果所有操作码都是 1 字节(我不确定,因为我不熟悉 NES,所以可能不需要)。然后一个简单的 std::map<char,int(*)(int)> opcodes
就足够了,而不是复杂的 naive_opcode
(或更好的树)实现。
查了一下,你似乎不需要树实现,但像这样的修改可能会有用:
template<int(* F)(int)>
int combined(int x){
return F(x);
}
template<int(* F)(int), int(* A)(int), int(*... G)(int)>
int combined(int x){
return F(combined<A, G...>(x));
}
这允许将许多效果相互组合,而不是 2 个。
我们可以使用模板创建通用 compose
函数,该函数使用捕获传入函数的 lambda 和 returns “组合”两个一元函数。
#include <functional>
#include <iostream>
template <typename Input, typename Output1, typename Output2>
std::function<Output2(Input)> compose(
std::function<Output2(Output1)> f,
std::function<Output1(Input)> g
) {
return [&f, &g](Input x) { return f(g(x)); };
}
int foo(int x) {
return x + 1;
}
int bar(int x) {
return x * 2;
}
int main() {
auto baz = compose<int, int, int>(foo, bar);
std::cout << baz(5) << std::endl;
auto wooble = compose<int, int, float>(
[](int x) { return static_cast<float>(x) + 1.5; },
[](int x) { return x * 3; }
);
std::cout << wooble(5) << std::endl;
return 0;
}
你想要这个吗?
int f1(int x) { return x + 1; }
int f2(int x) { return x * 2; }
int f3(int x) { return x * 3; }
int f4(int x) { return x - 5; }
int f5(int x) { return x + 9; }
int main() {
auto cf = combine<int>(f1, f2, f3, f4, f5);
std::cout << cf(5) << std::endl;
return 0;
}
Output:
40
完整代码:
#include <functional>
#include <concepts>
#include <iostream>
template<typename T, typename NUM = int>
concept num_processor = requires (T t, NUM x) {
{t(x)} -> std::same_as<NUM>;
};
template<typename NUM, num_processor p>
NUM process(NUM v, p proc) {
return proc(v);
}
template<typename NUM, num_processor p, num_processor... Funs>
NUM process(NUM v, p proc, Funs... funs) {
return process(proc(v), funs...);
}
template<typename NUM, num_processor... Funs>
std::function<NUM (NUM)> combine(Funs... funs) {
return [...funs = funs] (NUM v) { return process(v, funs...); };
}
int f1(int x) { return x + 1; }
int f2(int x) { return x * 2; }
int f3(int x) { return x * 3; }
int f4(int x) { return x - 5; }
int f5(int x) { return x + 9; }
int main() {
auto cf = combine<int>(f1, f2, f3, f4, f5);
std::cout << cf(5) << std::endl;
return 0;
}
编译为
-std=c++20
用于 gcc,/std:c++latest
用于 msvc
我想说的是,当你想为函数编写泛型时,切换到仿函数是一种“设计模式”:编译器旨在轻松处理类型,但处理你想要的东西的函数指针mis-match 并保持优化 compile-time 变得丑陋!
所以我们要么将我们的函数写成仿函数,要么我们包装它们为仿函数:
struct A
{
static constexpr int Func (int x)
{
return -3*x + 1;
}
};
struct B
{
static constexpr int Func (int x)
{
return -2*x - 5;
}
};
// etc...
如果我们在使用它们的方式上有很好的对称性,那么我们就可以系统地管理它们。例如。如果我们总是想像f(g(h(...y(z())...)))
那样把它们组合起来,那么我们可以这样解决:
template <class T, class ... Ts>
struct Combine
{
static constexpr int Get ()
{
int x = Combine<Ts...>::Get();
return T::Func(x);
}
};
template <class T>
struct Combine <T> // The base case: the last function in the list
{
static constexpr int Get ()
{
return T::Func();
}
};
或者,如果我们没有这样的运气,我们将不得不像您建议的那样求助于更多 old-fashioned 输入:
template <class Funcs, class Data>
constexpr int Combine (const Data & d)
{
Funcs F;
// Some use without much symmetry:
return F.f(F.g(d)) - F.h(d);
}
int main ()
{
struct FuncArgs
{
A f;
B g;
C h;
};
return Combine<FuncArgs>(5);
}
请注意,在第二个示例中,我已将静态方法更改为 non-static。这并不重要——无论如何,编译器都应该完全优化这些,但我认为在这种情况下,它会使语法稍微好一点(并显示另一种风格)。
大多数其他答案建议 std::function
,但我担心它需要的运行时开销。
由于您不需要 select 哪些函数是在运行时组合的,因此您可以不用它。我使用与 相同的想法,但针对任意类型进行了概括,并希望使用更好的语法:
#include <iostream>
#include <utility>
template <auto F0, auto ...F>
struct FuncList
{
static constexpr auto first = F0;
static constexpr bool have_next = true;
using next = FuncList<F...>;
};
template <auto F0>
struct FuncList<F0>
{
static constexpr auto first = F0;
static constexpr bool have_next = false;
};
template <typename F, typename ...P>
decltype(auto) Combine(P ...params) // Normally there would be `&&`, but I removed it allow casting to any function pointer type.
{
if constexpr (F::have_next)
return F::first(Combine<typename F::next, P &&...>(std::forward<P>(params)...));
else
return F::first(std::forward<P>(params)...);
}
int addone(int x)
{
return x + 1;
}
int multtwo(int x)
{
return 2 * x;
}
int main()
{
int (*ptr)(int) = Combine<FuncList<addone, multtwo>>;
std::cout << ptr(10) << '\n'; // 21
}
我正在使用 C++ 开发 NES 模拟器,我认为 运行 操作码最有效的方法是调用函数数组中的函数指针,这些函数指针完全执行操作码的功能。
问题是每个操作码都有特定的操作和内存地址。在寻找解决方案时,我偶然发现了 lambda 表达式。这对于现代硬件上的 NES 模拟器来说绝对足够好。但是,我找不到解决方案,使数组中的每个函数都包含用于操作和寻址的机器代码,而无需定义 256 个单独的函数。
这就是我对结合 f 和 g 的类似函数的想法:
int addone(int x) {
return x + 1;
}
int multtwo(int x) {
return 2 * x;
}
something combine(function<int(int)> f, function <int(int)> g) {
/* code here */
}
/*
combine(addone, multtwo) creates a function h that has the same machine code as
int h(x) {
return 2 * x + 1;
}
*/
有什么想法吗?如果非要我猜的话,它应该与模板有关。谢谢!
您可以这样做,使用 lambda 捕获两个函数并将函数分配给变量:
#include <functional>
#include <iostream>
int addone(int x){
return x + 1;
}
int multtwo(int x){
return x * 2;
}
std::function<int(int)> combine(std::function<int(int)> f, std::function<int(int)> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<int(int)>(tmp);
}
int main(){
auto h = combine(std::function<int(int)>(addone), std::function<int(int)>(multtwo)); // (2 * x) + 1
std::cout << h(10); // Prints 21
}
如果你想让它统一功能,可以使用模板:
#include <functional>
#include <iostream>
int addone(int x){
return x + 1;
}
int multtwo(int x){
return x * 2;
}
template <typename Func>
std::function<Func> combine(std::function<Func> f, std::function<Func> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<Func>(tmp);
}
int main(){
auto h = combine<int(int)>(std::function<int(int)>(addone), std::function<int(int)>(multtwo));
std::cout << h(10) << "\n"; // Prints 21
}
您也不需要指定类型,因为编译器可以计算出来:
#include <functional>
#include <iostream>
int addone(int x){
return x + 1;
}
int multtwo(int x){
return x * 2;
}
template <typename func>
std::function<Func> combine(std::function<Func> f, std::function<Func> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<Func>(tmp);
}
int main(){
auto h = combine(std::function<int(int)>(addone), std::function<int(int)>(multtwo));
std::cout << h(10) << "\n"; // Still prints 21
}
如果您想自动创建函数,请使用 2pichar 的答案和 for 循环,但对于模拟器,您可能需要类似 opcode->int(*)(int)
的东西。这可以通过一些 tree-like 结构来完成:
std::map<char, naive_opcode> opcodes;
struct naive_opcode {
std::map<char, naive_opcode> next;
int(* opcode_func)(int);
};
您可以通过以下方式解决此问题:
char data;
buf >> data;
naive_opcode opcode = opcodes[data];
while(!opcode.opcode_func){
buf >> data;
opcode = opcode.next[data];
}
opcode.opcode_func(param);
这当然会忽略错误,并且不包括指令指针和 .text
段内存之类的东西,而是将其替换为 buf
缓冲区以用于说明目的(在现实生活中的示例中,我希望这会被 data=memory[ip]; ++ip;
取代)。然后可以将其与以下实现相结合:
#include <iostream>
int addone(int x){
return x + 1;
}
int multtwo(int x){
return x * 2;
}
template<int(* F)(int), int(* G)(int)>
int combined(int x){
return F(G(x));
}
int main(){
std::cout << combined<addone,multtwo>(10);
}
你基本上可以将 naive_opcode
的结束节点定义为 {{}, combined<addone,multtwo>}
.
不幸的是,正如我在评论中提到的,这可能无法自动完成。我侦察的你能做的最好的事情就是定义如下内容:
std::vector<std::pair<const char*, int(*)(int)>> raw_opcodes = {{"\x10\x13", addone}, ...};
然后将其解析为树状结构。作为一个简短的旁注:如果所有操作码都是 1 字节(我不确定,因为我不熟悉 NES,所以可能不需要)。然后一个简单的 std::map<char,int(*)(int)> opcodes
就足够了,而不是复杂的 naive_opcode
(或更好的树)实现。
查了一下,你似乎不需要树实现,但像这样的修改可能会有用:
template<int(* F)(int)>
int combined(int x){
return F(x);
}
template<int(* F)(int), int(* A)(int), int(*... G)(int)>
int combined(int x){
return F(combined<A, G...>(x));
}
这允许将许多效果相互组合,而不是 2 个。
我们可以使用模板创建通用 compose
函数,该函数使用捕获传入函数的 lambda 和 returns “组合”两个一元函数。
#include <functional>
#include <iostream>
template <typename Input, typename Output1, typename Output2>
std::function<Output2(Input)> compose(
std::function<Output2(Output1)> f,
std::function<Output1(Input)> g
) {
return [&f, &g](Input x) { return f(g(x)); };
}
int foo(int x) {
return x + 1;
}
int bar(int x) {
return x * 2;
}
int main() {
auto baz = compose<int, int, int>(foo, bar);
std::cout << baz(5) << std::endl;
auto wooble = compose<int, int, float>(
[](int x) { return static_cast<float>(x) + 1.5; },
[](int x) { return x * 3; }
);
std::cout << wooble(5) << std::endl;
return 0;
}
你想要这个吗?
int f1(int x) { return x + 1; }
int f2(int x) { return x * 2; }
int f3(int x) { return x * 3; }
int f4(int x) { return x - 5; }
int f5(int x) { return x + 9; }
int main() {
auto cf = combine<int>(f1, f2, f3, f4, f5);
std::cout << cf(5) << std::endl;
return 0;
}
Output:
40
完整代码:
#include <functional>
#include <concepts>
#include <iostream>
template<typename T, typename NUM = int>
concept num_processor = requires (T t, NUM x) {
{t(x)} -> std::same_as<NUM>;
};
template<typename NUM, num_processor p>
NUM process(NUM v, p proc) {
return proc(v);
}
template<typename NUM, num_processor p, num_processor... Funs>
NUM process(NUM v, p proc, Funs... funs) {
return process(proc(v), funs...);
}
template<typename NUM, num_processor... Funs>
std::function<NUM (NUM)> combine(Funs... funs) {
return [...funs = funs] (NUM v) { return process(v, funs...); };
}
int f1(int x) { return x + 1; }
int f2(int x) { return x * 2; }
int f3(int x) { return x * 3; }
int f4(int x) { return x - 5; }
int f5(int x) { return x + 9; }
int main() {
auto cf = combine<int>(f1, f2, f3, f4, f5);
std::cout << cf(5) << std::endl;
return 0;
}
编译为
-std=c++20
用于 gcc,/std:c++latest
用于 msvc
我想说的是,当你想为函数编写泛型时,切换到仿函数是一种“设计模式”:编译器旨在轻松处理类型,但处理你想要的东西的函数指针mis-match 并保持优化 compile-time 变得丑陋!
所以我们要么将我们的函数写成仿函数,要么我们包装它们为仿函数:
struct A
{
static constexpr int Func (int x)
{
return -3*x + 1;
}
};
struct B
{
static constexpr int Func (int x)
{
return -2*x - 5;
}
};
// etc...
如果我们在使用它们的方式上有很好的对称性,那么我们就可以系统地管理它们。例如。如果我们总是想像f(g(h(...y(z())...)))
那样把它们组合起来,那么我们可以这样解决:
template <class T, class ... Ts>
struct Combine
{
static constexpr int Get ()
{
int x = Combine<Ts...>::Get();
return T::Func(x);
}
};
template <class T>
struct Combine <T> // The base case: the last function in the list
{
static constexpr int Get ()
{
return T::Func();
}
};
或者,如果我们没有这样的运气,我们将不得不像您建议的那样求助于更多 old-fashioned 输入:
template <class Funcs, class Data>
constexpr int Combine (const Data & d)
{
Funcs F;
// Some use without much symmetry:
return F.f(F.g(d)) - F.h(d);
}
int main ()
{
struct FuncArgs
{
A f;
B g;
C h;
};
return Combine<FuncArgs>(5);
}
请注意,在第二个示例中,我已将静态方法更改为 non-static。这并不重要——无论如何,编译器都应该完全优化这些,但我认为在这种情况下,它会使语法稍微好一点(并显示另一种风格)。
大多数其他答案建议 std::function
,但我担心它需要的运行时开销。
由于您不需要 select 哪些函数是在运行时组合的,因此您可以不用它。我使用与
#include <iostream>
#include <utility>
template <auto F0, auto ...F>
struct FuncList
{
static constexpr auto first = F0;
static constexpr bool have_next = true;
using next = FuncList<F...>;
};
template <auto F0>
struct FuncList<F0>
{
static constexpr auto first = F0;
static constexpr bool have_next = false;
};
template <typename F, typename ...P>
decltype(auto) Combine(P ...params) // Normally there would be `&&`, but I removed it allow casting to any function pointer type.
{
if constexpr (F::have_next)
return F::first(Combine<typename F::next, P &&...>(std::forward<P>(params)...));
else
return F::first(std::forward<P>(params)...);
}
int addone(int x)
{
return x + 1;
}
int multtwo(int x)
{
return 2 * x;
}
int main()
{
int (*ptr)(int) = Combine<FuncList<addone, multtwo>>;
std::cout << ptr(10) << '\n'; // 21
}