C++11中等同于std::apply()的功能是什么?(另外,如何在成员函数上执行它)

huangapple go评论69阅读模式
英文:

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

问题

Here's the translation of the provided text:

我有下面的RPC演示程序,但我卡在了将参数可靠地推送到管道中并以相同顺序拉出它们的问题上。

  • 请参阅Serialize()Invoke()(全局版本和类中心版本)
  • Serialize()的“初始化器列表”方法按从左到右的顺序存储参数。
  • Invoke()的“参数包扩展”方法按从右到左的顺序读取它们,导致它们以相反的顺序。
    • 问题1: 我认为这是与实现有关的
    • 解决它需要使用Reverser类按相反的顺序推送参数,以便在以从右到左的顺序拉出它们时它们是正确的参数。
  • “初始化器列表”的Invoke()调用从左到右获取参数,但似乎需要使用std::apply()来调用函数。
    • 问题2: 我不能升级到C++17,所以是否有一个可以执行std::apply()的C++11机制?(还可以用于成员函数,我找不到语法)

简而言之:我正在尝试创建一些可靠的、非特定于实现的模板函数,以按照函数签名的顺序将它们的参数存储到管道中并稍后恢复它们并调用函数。(由函数的签名驱动)。如果一切都失败了,我有一个可工作的解决方案(使用Reverser类),只是感觉有点笨拙。

您提供的代码链接在此处:https://godbolt.org/z/bh4snvn57

英文:

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

答案1

得分: 2

大量的模板代码在前方。你可能想要仔细检查 Boost 是否提供类似的内容,这很可能。这里是简化的解决方案。

注意它并非完全完成,即:

a. INVOKE 没有考虑 std::reference_wrapper 等全部细节。

b. apply 为简洁起见以值传递元组。需要添加所有必要的左值、常值和右值重载。

#include <tuple>
#include <utility>
#include <iostream>

namespace std14
{
    // ...(整数序列的实现,未提供完整代码)
}

template <class F, class... Args>
constexpr auto INVOKE(F&& f, Args&&... args) ->
    decltype(std::forward<F>(f)(std::forward<Args...>(args)...)) {
      return std::forward<F>(f)(std::forward<Args...>(args)...);
}

// ...(其他代码)

struct S
{
    char f(int i) const { return 'a' + 1; }
    char g(char i, int j) const { return j + i; }
};

struct F
{
    void operator()(int i) { std::cout << i << '\n'; }
};

int add(int i) { return i + 2; }

int main()
{
    // ...(主函数中的代码)
}

索引序列的实现来自此处:https://gist.github.com/ntessore/dc17769676fb3c6daa1f
演示:https://godbolt.org/z/e1Wfj85q7

顺便说一句,可以在这里找到关于还原元组的信息。

英文:

Massive amounts of boilerplate on the road ahead. You might want to double-check if Boost offers something similar, it's likely. The simplified solution here.

Note that it's not entirely complete, i.e.:

a. INVOKE does not take all the subtelties of std::reference_wrapper and the like into account.

b. apply accepts the tuple by value for brevity. All the necessary lvalue-const-rvalue overloads need to be added.

#include &lt;tuple&gt;
#include &lt;utility&gt;
#include &lt;iostream&gt;

namespace std14
{
	template&lt;typename T, T... Ints&gt;
	struct integer_sequence
	{
		typedef T value_type;
		static constexpr std::size_t size() { return sizeof...(Ints); }
	};
	
	template&lt;std::size_t... Ints&gt;
	using index_sequence = integer_sequence&lt;std::size_t, Ints...&gt;;
	
	template&lt;typename T, std::size_t N, T... Is&gt;
	struct make_integer_sequence : make_integer_sequence&lt;T, N-1, N-1, Is...&gt; {};
	
	template&lt;typename T, T... Is&gt;
	struct make_integer_sequence&lt;T, 0, Is...&gt; : integer_sequence&lt;T, Is...&gt; {};
	
	template&lt;std::size_t N&gt;
	using make_index_sequence = make_integer_sequence&lt;std::size_t, N&gt;;
	
	template&lt;typename... T&gt;
	using index_sequence_for = make_index_sequence&lt;sizeof...(T)&gt;;
}


template &lt;class F, class... Args&gt;
constexpr auto INVOKE(F&amp;&amp; f, Args&amp;&amp;... args) -&gt;
    decltype(std::forward&lt;F&gt;(f)(std::forward&lt;Args...&gt;(args)...)) {
      return std::forward&lt;F&gt;(f)(std::forward&lt;Args...&gt;(args)...);
}

template&lt;typename Fptr, typename Class, typename... Args&gt;
constexpr auto INVOKE(Fptr fptr, Class&amp;&amp; obj, Args&amp;&amp; ...args)
-&gt; decltype((std::forward&lt;Class&gt;(obj).*fptr)(std::forward&lt;Args&gt;(args)...))
{
    return (std::forward&lt;Class&gt;(obj).*fptr)(std::forward&lt;Args&gt;(args)...);
}


template&lt;typename F, typename Tuple, std::size_t...I&gt;
constexpr auto apply_impl(F&amp;&amp; f, Tuple&amp;&amp; t, std14::index_sequence&lt;I...&gt;)
-&gt; decltype(INVOKE(std::forward&lt;F&gt;(f), std::get&lt;I&gt;(std::forward&lt;Tuple&gt;(t))...))
{
    return INVOKE(std::forward&lt;F&gt;(f), std::get&lt;I&gt;(std::forward&lt;Tuple&gt;(t))...);
}



template&lt;typename F, typename ...Args&gt;
constexpr auto apply(F&amp;&amp; f, std::tuple&lt;Args...&gt; tup)
-&gt; decltype(apply_impl(
        std::forward&lt;F&gt;(f),
        std::forward&lt;std::tuple&lt;Args...&gt;&gt;(tup),
        std14::make_index_sequence&lt;sizeof...(Args)&gt;{}
    ))
{
    return apply_impl(
        std::forward&lt;F&gt;(f),
        std::forward&lt;std::tuple&lt;Args...&gt;&gt;(tup),
        std14::make_index_sequence&lt;sizeof...(Args)&gt;{}
    ); 
}


struct S
{
    char f(int i)  const { return &#39;a&#39; + 1;}
    char g(char i, int j)  const { return j + i;}
};

struct F
{
    void operator()(int i) {std::cout &lt;&lt;i &lt;&lt; &#39;\n&#39;;}
};

int add(int i) { return i + 2;}

int main()
{
    S s{};
    std::cout &lt;&lt; INVOKE(&amp;S::f, s, 5) &lt;&lt; &#39;\n&#39;;   
    std::cout &lt;&lt; INVOKE(&amp;S::g, s, &#39;b&#39;, 1) &lt;&lt; &#39;\n&#39;;   
    INVOKE(F{}, 8);
    std::tuple&lt;S&amp;, char, int&gt; t {s, &#39;c&#39;, 2};
    std::cout &lt;&lt; apply(&amp;S::g, t) &lt;&lt;&#39;\n&#39;;
    std::cout &lt;&lt; INVOKE(&amp;add, 5) &lt;&lt;&#39;\n&#39;;

    return 0;
}

Index sequence implementation taken from here: https://gist.github.com/ntessore/dc17769676fb3c6daa1f
Demo: https://godbolt.org/z/e1Wfj85q7

BTW, have a look here for reverting the tuple.

huangapple
  • 本文由 发表于 2023年5月22日 06:10:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76302127.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定