dimanche 21 mai 2023

C++11 equivalent of std::apply()? (Plus, how to do it on member functions)

I have my RPC demo program below, but I'm stuck with the problems of reliably pushing parameters into the pipe and pulling them out in the same order.

  • see Serialize() and Invoke() (both the global version and the class centric ones)
  • The "initializer list" method of Serialize() stores the parameters left to right.
  • the "parameter pack expansion" method of Invoke() reads them right to left, which leaves them in reverse order.
    • Problem 1: I believe this is implementation specific
    • Solving it required pushing the parameters in the reverse order with the Reverser class so that when they were pulled out in the right to left order they were in the right parameters.
  • the "initializer list" Invoke() calls the gets the parameters from left to right, but seems to require std::apply() to call the function.
    • Problem 2: I can't upgrade to C++17, so is there a C++11 mechanism that can do what std::apply() is doing? (plus work on member functions as well, which I couldn't figure out the syntax)

TL;DR: I'm trying to come up with a reliable, non-implementation specific way to create some template functions to store their parameters into a pipe and restore them later in the same order and call the function. (All driven by the function's signature).

If all else fails, I have a working solution(using the Reverser class), it just feels hacky.

godbolt here: https://godbolt.org/z/bh4snvn57

#include <stdint.h>
#include <stdio.h>
#include <tuple>
#include <iostream>
#include <cstring>

#define REVERSER 1
#define SHOW_PUSH_PULL 0

//////////////////////////////////////////////////////////////////////
//string hashing operator and function
namespace detail
{
    // FNV-1a 32bit hashing algorithm.
    inline constexpr uint32_t fnv1a_32(char const *s, size_t count) 
    {
        return count ? (fnv1a_32(s, count - 1) ^ s[count - 1]) * 16777619u : 2166136261u;
    }
}    // namespace detail
inline constexpr uint32_t operator"" _hash(const char *  s, size_t count)
{
    return detail::fnv1a_32(s, count);
}
constexpr uint32_t hash(char const *s,size_t count)
{
    return detail::fnv1a_32(s,count);
}
//////////////////////////////////////////////////////////////////////

#if REVERSER
///////////////////////////////////////////////////////////////////////////////////
//Reverser class to reverse the order of parameters 
class Reverser
{
    uint8_t storage[1024+1];
    uint8_t *writeptr;
public:        
    template<typename T>
    void push(T &data)
    {
#if SHOW_PUSH_PULL         
        printf("    Push(%s)\n",typeid(data).name());
#endif        
        writeptr -= sizeof(data);
        memcpy(writeptr,&data,sizeof(data));
    }
    Reverser() {
        writeptr = &storage[1024];
    }
    void GetPtr(uint8_t *&ptr, size_t &bytes)
    {
        ptr = writeptr;
        bytes = uintptr_t(&storage[1024]) - uintptr_t(writeptr);
    }
};
///////////////////////////////////////////////////////////////////////////////////
#endif

///////////////////////////////////////////////////////////////////////////////////
//The "IPC" mechanism
class ByteQueue
{
    uint8_t storage[1024];
    uint8_t *writeptr;
public:        
    void push(uint8_t *pPtr, size_t bytes)    
    {
        memcpy(writeptr,pPtr,bytes);
        writeptr += bytes;
    }
    template<typename T>
    void push(T &data)
    {
        memcpy(writeptr,&data,sizeof(data));
        writeptr += sizeof(data);
    }
    template<typename T>
    void pull(T&data)
    {
        memcpy(&data,storage,sizeof(data));
        uint32_t uAmountToCopy = uintptr_t(writeptr)-uintptr_t(storage)-sizeof(data);
        memmove(storage,storage+sizeof(data),uAmountToCopy);
        writeptr -= sizeof(data);
    }
    ByteQueue() {
        writeptr = storage;
    }
};
ByteQueue g_ByteQueue;


void send_to_IPC_Pipe(uint8_t *pPtr, size_t uLength)
{
   g_ByteQueue.push(pPtr,uLength);
}


template<typename T>
void send_to_IPC_Pipe(T data)
{
#if SHOW_PUSH_PULL && !REVERSER
    printf("    Push(%s)\n",typeid(data).name());
#endif    
    g_ByteQueue.push(data);
}

template<typename T>
T get_from_IPC_Pipe()
{
    T var;
#if SHOW_PUSH_PULL
    printf("    Pull(%s)\n",typeid(T).name());
#endif
    g_ByteQueue.pull(var);
    return var;
}

template<typename ... Args>
void Serialize(uint32_t FunctionID, Args ... args)
{
    send_to_IPC_Pipe(FunctionID);
#if REVERSER    
    Reverser MyReverser;
    
    //push it into the reverser (oddly, it seems the parameters are parsed
    //in reverse order on the way Invoke() call, so we have to reverse them)
    //some magical syntax for C++11
    int dummy[]= { 0,(MyReverser.push(args),0)...};
    //hide the warning
    (void)dummy;

    uint8_t *pPtr;
    size_t uLength;
    MyReverser.GetPtr(pPtr,uLength);
    send_to_IPC_Pipe(pPtr,uLength);
#else
    int dummy[]= { 0,(send_to_IPC_Pipe(args),0)...};
    //hide the warning
    (void)dummy;
#endif
}

template<typename ... Args>
void Invoke(void(*function)(Args...))
{
#if REVERSER    
    function(get_from_IPC_Pipe<Args>()...);
#else
    std::tuple<Args...> args {get_from_IPC_Pipe<Args>()...};
    std::apply(function,std::move(args));    
#endif
}
//////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
void MyFunction(int a, float b, bool c)
{
    printf("ClientSide: %s, %d, %f, %i\n",__PRETTY_FUNCTION__,a,b,c);
    Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b,c);
}
void MyFunctionServerSide(int a, float b, bool c)
{
    printf("ServerSide: %s, %d, %f, %i\n",__PRETTY_FUNCTION__,a,b,c);
}

void MyFunction2(int a, float b, bool c)
{
    printf("ClientSide: %s, %d, %f, %i\n",__PRETTY_FUNCTION__,a,b,c);
    Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b,c);
}
void MyFunction2ServerSide(int a, float b, bool c)
{
    printf("ServerSide: %s, %d, %f, %i\n",__PRETTY_FUNCTION__,a,b,c);
}

void MyFunction3(float a, float b)
{
    printf("ClientSide: %s, %f, %f\n",__PRETTY_FUNCTION__,a,b);
    Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b);
}
void MyFunction3ServerSide(float a, float b)
{
    printf("ServerSide: %s, %f, %f\n",__PRETTY_FUNCTION__,a,b);
}
///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
void MyFunction4()
{
    printf("ClientSide: %s\n",__PRETTY_FUNCTION__);
    Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)));
}
void MyFunction4ServerSide()
{
    printf("ServerSide: %s\n",__PRETTY_FUNCTION__);
}
///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
struct ClientSideClass
{
    template<typename ... Args>
    void Serialize(uint32_t FunctionID, Args ... args)
    {
        auto *pName = typeid(*this).name();
        uint32_t uClassID = hash(pName,strlen(pName));
        send_to_IPC_Pipe(uClassID);
        send_to_IPC_Pipe(FunctionID);
#if REVERSER        
        Reverser MyReverser;
        
        //push it into the reverser (oddly, it seems the parameters are parsed
        //in reverse order on the way Invoke() call, so we have to reverse them)
        //some magical syntax for C++11
        int dummy[]= { 0,(MyReverser.push(args),0)...};
        //hide the warning
        (void)dummy;

        uint8_t *pPtr;
        size_t uLength;
        MyReverser.GetPtr(pPtr,uLength);
        send_to_IPC_Pipe(pPtr,uLength);
#else
        int dummy[]= { 0,(send_to_IPC_Pipe(args),0)...};
        //hide the warning
        (void)dummy;

#endif
    }    
    void Method1(int a)
    {
        printf("ClientSide: %s: %d\n",__PRETTY_FUNCTION__,a);
        Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a);
    }
    void Method1(int a,int b)
    {
        printf("ClientSide: %s: %d,%d\n",__PRETTY_FUNCTION__,a,b);
        Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b);
    }

    void Method2(int a,int b)
    {
        printf("ClientSide: %s: %d,%d\n",__PRETTY_FUNCTION__,a,b);
        Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b);
    }

};

struct ServerSideClass
{
    template<typename ... Args>
    void Invoke(void(ServerSideClass::*function)(Args...))
    {
#if REVERSER        
        (*this.*(function))(get_from_IPC_Pipe<Args>()...);
#else
        std::tuple<Args...> args {get_from_IPC_Pipe<Args>()...};
        std::apply(function,std::move(args));
#endif
    }    
    void Method1(int a)       { printf("ServerSide: %s: %d\n",__PRETTY_FUNCTION__,a); }
    void Method1(int a,int b) { printf("ServerSide: %s: %d,%d \n",__PRETTY_FUNCTION__,a,b); }
    void Method2(int a,int b) { printf("ServerSide: %s: %d,%d \n",__PRETTY_FUNCTION__,a,b); }
    void InvokeIt()
    {
        uint32_t uFunctionID = get_from_IPC_Pipe<uint32_t>();
        switch (uFunctionID)
        {
            case "void ClientSideClass::Method1(int)"_hash:
                {
                    void (ServerSideClass::*function)(int) = &ServerSideClass::Method1;
                    Invoke(function);
                }
                break;                    
            case "void ClientSideClass::Method1(int, int)"_hash:
                {
                    void (ServerSideClass::*function)(int,int) = &ServerSideClass::Method1;
                    Invoke(function);
                }
                break;                    
            case "void ClientSideClass::Method2(int, int)"_hash:
                Invoke(&ServerSideClass::Method2);
                break;                    
            default:
                printf("Unknown method\n");
        }
    }
};

///////////////////////////////////////////////////////////
ServerSideClass g_ServerSide;
void runRPCs()
{
    uint32_t uFunctionID = get_from_IPC_Pipe<uint32_t>();
//    printf("runRPC:function id(%u)\n",uFunctionID);
    switch (uFunctionID)
    {
        case "15ClientSideClass"_hash:
            g_ServerSide.InvokeIt();
            break;
        case "void MyFunction(int, float, bool)"_hash:
            Invoke(MyFunctionServerSide);
            break;
        case "void MyFunction2(int, float, bool)"_hash:
            Invoke(MyFunction2ServerSide);
            break;
        case "void MyFunction3(float, float)"_hash:
            Invoke(MyFunction3ServerSide);
            break;
        case "void MyFunction4()"_hash:
            Invoke(MyFunction4ServerSide);
            break;
        default:
            printf("Unknown function id\n");
            break;
    }
}

int main()
{
    ClientSideClass client;
//    auto *pName = typeid(ClientSide).name();
//    printf("--%s--\n",pName);
//    printf("%u\n","10ClientSide"_hash);
//    printf("%u\n",hash(pName,strlen(pName)));
    MyFunction(2,4.33,true);
    MyFunction4();
    MyFunction2(4,-4.33,false);
    MyFunction3(3.144,-4.33);
    client.Method1(5);
    client.Method1(3,7);
    client.Method2(8,9);
    runRPCs();
    runRPCs();
    runRPCs();
    runRPCs();
    runRPCs();
    runRPCs();
    runRPCs();
    return 0;
}

Aucun commentaire:

Enregistrer un commentaire