英文:
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机制?(还可以用于成员函数,我找不到语法)
- 问题2: 我不能升级到C++17,所以是否有一个可以执行
简而言之:我正在尝试创建一些可靠的、非特定于实现的模板函数,以按照函数签名的顺序将它们的参数存储到管道中并稍后恢复它们并调用函数。(由函数的签名驱动)。如果一切都失败了,我有一个可工作的解决方案(使用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()
andInvoke()
(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 requirestd::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)
- Problem 2: I can't upgrade to C++17, so is there a C++11 mechanism that can do what
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 <tuple>
#include <utility>
#include <iostream>
namespace std14
{
template<typename T, T... Ints>
struct integer_sequence
{
typedef T value_type;
static constexpr std::size_t size() { return sizeof...(Ints); }
};
template<std::size_t... Ints>
using index_sequence = integer_sequence<std::size_t, Ints...>;
template<typename T, std::size_t N, T... Is>
struct make_integer_sequence : make_integer_sequence<T, N-1, N-1, Is...> {};
template<typename T, T... Is>
struct make_integer_sequence<T, 0, Is...> : integer_sequence<T, Is...> {};
template<std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;
template<typename... T>
using index_sequence_for = make_index_sequence<sizeof...(T)>;
}
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)...);
}
template<typename Fptr, typename Class, typename... Args>
constexpr auto INVOKE(Fptr fptr, Class&& obj, Args&& ...args)
-> decltype((std::forward<Class>(obj).*fptr)(std::forward<Args>(args)...))
{
return (std::forward<Class>(obj).*fptr)(std::forward<Args>(args)...);
}
template<typename F, typename Tuple, std::size_t...I>
constexpr auto apply_impl(F&& f, Tuple&& t, std14::index_sequence<I...>)
-> decltype(INVOKE(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...))
{
return INVOKE(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename ...Args>
constexpr auto apply(F&& f, std::tuple<Args...> tup)
-> decltype(apply_impl(
std::forward<F>(f),
std::forward<std::tuple<Args...>>(tup),
std14::make_index_sequence<sizeof...(Args)>{}
))
{
return apply_impl(
std::forward<F>(f),
std::forward<std::tuple<Args...>>(tup),
std14::make_index_sequence<sizeof...(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()
{
S s{};
std::cout << INVOKE(&S::f, s, 5) << '\n';
std::cout << INVOKE(&S::g, s, 'b', 1) << '\n';
INVOKE(F{}, 8);
std::tuple<S&, char, int> t {s, 'c', 2};
std::cout << apply(&S::g, t) <<'\n';
std::cout << INVOKE(&add, 5) <<'\n';
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论