简体   繁体   English

有没有办法在 C++ 中创建一个组合函数的数组?

[英]Is there a way to create an array of combined functions compile-time in c++?

I'm working on an NES emulator in c++ and figured that the most efficient way to run opcodes would be to call a function pointer in an array of functions that do exactly what the opcode does.我正在使用 C++ 开发 NES 仿真器,并认为运行操作码的最有效方法是调用函数数组中的函数指针,这些函数完全执行操作码的功能。

The problem is that each opcode has a specific operation and memory address.问题是每个操作码都有特定的操作和内存地址。 While searching for a solution, I stumbled upon lambda expressions.在寻找解决方案时,我偶然发现了 lambda 表达式。 This is definitely good enough for a NES emulator on modern hardware.这对于现代硬件上的 NES 模拟器来说绝对足够好。 However, I can't find a solution such that each function in the array contains the machine code for both the operation and the addressing without defining 256 separate functions.但是,我找不到这样的解决方案,即数组中的每个函数都包含操作和寻址的机器代码,而不定义 256 个单独的函数。

This is along what I had in mind for a similar function that combines f and g:这与我对结合 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;
}
*/

Any ideas?有任何想法吗? If I had to take a guess, it would have something to do with templates.如果我不得不猜测,那将与模板有关。 Thanks!谢谢!

I'd say that when you want to write generics for functions that it's kind of a "design pattern" to switch to functors: Compilers are desigined to handle types easily, but handling function pointers for stuff you want to mis-match and keep optimised at compile-time gets ugly!我想说的是,当您想为函数编写泛型时,切换到函子是一种“设计模式”:编译器旨在轻松处理类型,但为您想要不匹配并保持优化的内容处理函数指针在编译时变得丑陋!

So we either write our functions as functors, or we wrap them as functors:所以我们要么把函数写成函子,要么它们包装成函子:

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

If we have nice symmetry in how we'll use them, then we can manage them systematically.如果我们在如何使用它们方面有很好的对称性,那么我们就可以系统地管理它们。 Eg.例如。 if we always want to combine them like f(g(h(...y(z())...))) , then we can solve as follows:如果我们总是想像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();
    }
};

demo演示

Or if we're in no such luck, we'll have to resort to more old-fashioned inputs like you suggested:或者,如果我们没有这样的运气,我们将不得不像您建议的那样求助于更老式的输入:

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

demo演示

Note that in the second example I've changed from static methods to non-static.请注意,在第二个示例中,我已从静态方法更改为非静态方法。 This doesn't really matter - the compiler should optimise these fully regardless, but I think in this case it makes the syntax slightly nicer (and shows an alternative style).这并不重要 - 编译器无论如何都应该完全优化这些,但我认为在这种情况下它会使语法稍微好一点(并显示另一种风格)。

You can do something like this, using a lambda to capture the two functions and to assign a function to a variable:你可以做这样的事情,使用 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
}

If you want it to generally combine the functions, you can use a template:如果您希望它通常组合功能,您可以使用模板:

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

You also don't need to specify the type, since the compiler can figure it out:您也不需要指定类型,因为编译器可以弄清楚:

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

If you want to create automatically the functions, use 2pichar's answer with a for loop, but for an emulator you'd probably want something like opcode->int(*)(int) .如果您想自动创建函数,请使用 2pichar 的答案和 for 循环,但对于模拟器,您可能需要类似opcode->int(*)(int)的东西。 This could be done by some tree-like structure:这可以通过一些树状结构来完成:

std::map<char, naive_opcode> opcodes;
struct naive_opcode {
    std::map<char, naive_opcode> next;
    int(* opcode_func)(int);
};

You'd work through this in some fashion like:你会以某种方式解决这个问题,例如:

char data;
buf >> data;
naive_opcode opcode = opcodes[data];
while(!opcode.opcode_func){
    buf >> data;
    opcode = opcode.next[data];
}
opcode.opcode_func(param);

This of course ignores errors and does not include things like the instruction pointer and the .text section memory, rather replacing it with the buf buffer for illustrative purposes (In a real life example I'd expect this to be replaced by data=memory[ip]; ++ip; ).这当然会忽略错误,并且不包括指令指针和.text部分内存之类的内容,而是出于说明目的将其替换为buf缓冲区(在现实生活中的示例中,我希望将其替换为data=memory[ip]; ++ip; )。 This could then be combined with an implementation like:然后可以将其与以下实现相结合:

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

for which you could essentially just define the end node of naive_opcode as {{}, combined<addone,multtwo>} .为此,您基本上可以将naive_opcode的结束节点定义为{{}, combined<addone,multtwo>}

Unfortunately as I mentioned in my comment, this probably cannot be done automatically.不幸的是,正如我在评论中提到的,这可能无法自动完成。 The best you could do I recon is that you define something like:我侦察你能做的最好的事情是你定义如下:

std::vector<std::pair<const char*, int(*)(int)>> raw_opcodes = {{"\x10\x13", addone}, ...};

and then parse that into the tree like structure.然后将其解析为树状结构。 As a brief side note: this might not be needed if all the opcodes are 1 byte (which I am unsure about since I am not familiar with NES).作为一个简短的旁注:如果所有操作码都是 1 字节,则可能不需要这(我不确定,因为我不熟悉 NES)。 Then a simple std::map<char,int(*)(int)> opcodes will suffice instead of the convoluted naive_opcode (or better tree) implementation.然后一个简单的std::map<char,int(*)(int)> opcodes就足够了,而不是复杂的naive_opcode (或更好的树)实现。

Looked it up and it seems that you wouldn't need the tree implementation, but a modification like this can be useful:查了一下,似乎你不需要树实现,但是这样的修改可能很有用:

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

This allows for combining many effects into each other, rather than 2.这允许将许多效果组合在一起,而不是 2 个。

We can use templates to create a generic compose function that "combines" two unary functions using a lambda that captures the functions passed in, and returns it.我们可以使用模板创建一个通用的compose函数,该函数使用 lambda 来“组合”两个一元函数,该 lambda 捕获传入的函数并返回它。

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

Do you want this?你想要这个吗?

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

Full code:完整代码:

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

Compile with -std=c++20 for gcc and /std:c++latest for msvc使用-std=c++20编译 gcc 和/std:c++latest msvc

Most other answers suggest std::function , but I'm wary of the runtime overhead it requires.大多数其他答案都建议std::function ,但我对它所需的运行时开销持谨慎态度。

Since you don't need to select which functions are composed at runtime, you can do it without it.由于您不需要选择在运行时组合哪些函数,因此您可以不使用它。 I'm using the same idea as @Elliot , but generalized for arbitrary types, and with hopefully nicer syntax:我使用与@Elliot相同的想法,但适用于任意类型,并且希望语法更好:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM