我有以下代码从指针读取值/参数并调用函数:
#include <iostream>
#include <functional>
#include <string>
#include <tuple>
template<typename T>
T Read(void*& ptr)
{
T result = *static_cast<T*>(ptr);
ptr = static_cast<T*>(ptr) + 1;
return result;
}
template<typename T>
void Write(void*& ptr, const T &value)
{
*static_cast<T*>(ptr) = value;
ptr = static_cast<T*>(ptr) + 1;
}
template<typename R, typename... Args>
R Call(void* arguments, R (*func)(Args...))
{
//args = [c, b, a] somehow..?
auto args = std::make_tuple(Read<std::decay_t<Args>>(arguments)...);
return std::apply(func, args);
}
void func_one(int a, int b, int c)
{
std::cout<<"a: "<<a<<" b: "<<b<<" c: "<<c<<"\n";
}
int main()
{
int a = 1024;
int b = 2048;
int c = 3072;
int* args = new int[3];
void* temp = args;
Write(temp, a);
Write(temp, b);
Write(temp, c);
Call(args, func_one);
delete[] args;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我这样做:
auto args = std::tuple<Args...>();
std::apply([&arguments](auto&... args) {((args = Read<std::decay_t<decltype(args)>>(arguments)), ...);}, args);
Run Code Online (Sandbox Code Playgroud)
然后args = [a, b, c]。它的顺序正确。在后面的代码中,我使用逗号运算符读取每个值并将其分配给元组。
我试过:std::invoke(func, Read<std::decay_t<Args>>(arguments)...);
这导致参数的顺序也相反。
那么..下面两者有什么区别:
Read<Args>(arguments)... //tuple is reversed - a: 3072 b: 2048 c: 1024
//and
(Read<Args>(arguments)), ...) //tuple is in the right order - a: 1024 b: 2048 c: 3072
Run Code Online (Sandbox Code Playgroud)
为什么第一个以相反的顺序创建一个元组?有没有更好的方法std::apply来获得正确的顺序?
函数调用表达式中的参数是不确定顺序的 ( [expr.call]/8)。也就是说,在std::make_tuple(Read<int>(arguments), Read<int>(arguments), Read<int>(arguments))代码中的调用中,Read<int>(arguments)调用按照实现想要执行的任何顺序发生,而不是按照它们编写的顺序发生。这不是未定义的行为,因为调用之间存在序列点,因此您不会尝试arguments“一次”多次修改。只是标准定义了程序有六种可能的行为(执行调用的不同顺序),并且没有指定发生哪一种(甚至没有指定在同一实现下编译/运行/调用之间是一致的) !)。请注意,涉及参数包这一事实不会改变任何内容:包扩展是通过“插入”扩展语法来代替扩展来处理的,然后根据非包规则进行处理([temp.variadic]/8)。
当您使用“固定”版本时,代码将扩展为基本上包含
arg1 = Read<int>(arguments), arg2 = Read<int>(arguments), arg3 = Read<int>(arguments);
Run Code Online (Sandbox Code Playgroud)
其中,s 现在是逗号运算符,而不是函数调用语法的一部分。逗号运算符的操作数从左到右确定地排序。
老实说,我不知道有什么比你所做的更简单的方法来获得正确的订单。实际上,我认为我们遇到了不幸的情况,如果函数调用的参数表达式具有必须按确定的顺序排序的副作用,那么就无法直接从参数表达式初始化函数参数(没有副本或移动)。当然,这在这里不是问题。尽管如此,您的“固定”版本还是提出了不必要的要求,即所涉及的类型也是默认可构造和可分配的,这是不必要的。请注意,无论出于何种原因,如果您使用大括号(列表初始化)来调用类的构造函数(在我们的示例中为std::tuple),则参数表达式计算将按从左到右的顺序进行排序,保证 ( [dcl.init.list]/4)。
template<typename R, typename... Args>
R Call(void *arguments, R (*func)(Args...)) {
std::tuple<std::decay_t<Args>...> args{Read<std::decay_t<Args>>(arguments)...};
return std::apply(func, std::move(args));
}
Run Code Online (Sandbox Code Playgroud)
我不知道正常函数调用的类似物,所以我们仍然会发生移动,这很遗憾(尽管我希望您没有编写具有昂贵移动的类型!)。
另外,我相信你已经知道这一点,但请注意,整个演员阵容void*以及所有这些看起来非常不安全。特别是,即使假设所有这些函数的调用者都“正确”地执行了操作,函数本身也没有正确处理对齐(或者根本...)。
| 归档时间: |
|
| 查看次数: |
65 次 |
| 最近记录: |