问题描述
本质上,我需要将参数传递给 C++ 可变参数模板化函数。该函数具有以下通用签名/形式:
template <typename... Options>
ReaderType GetAList(std::string const& name,Options&&... options)
{ /*... do stuff ...*/ }
我已经阅读了几个关于完美转发、可变参数模板、动态参数列表等的 StackOverflow 问答。特别是这些:
C++ generic function call with varargs parameter
Dynamically creating a C++ function argument list at runtime
Create function call dynamically in C++
C++ forwarding reference and r-value reference
这些只是我读过的关于这个主题的少数 StackOverflow 线程(如果你愿意的话)。我还在 LinkedIn 网站上发现了一篇关于可变参数模板和函数的有趣文章:
Modern C++ and Variadic Functions: How to Shoot Yourself in the Foot and How to Avoid It
说这个的重点是我已经尝试做功课了,但是还没有结果。于是我决定问一个问题。
这里的问题是这些选项直到运行时才知道。因此,我必须动态地向 GetAList
方法调用添加参数。在上面的例子中,参数在编译时是已知的,例如:
interceptor(gWrappers[1],10.12,12.10); // <--- example 1: params are given statically at compile time
func_map["printall"]("iic",5,10,'x'); // <--- example 2: same approach
这在我的场景中不起作用,因为我不知道用户将从可用选项中选择哪些选项。所以我必须看看他们要使用哪些选项,并动态形成一个参数列表。
在动态参数生成的其他示例中(这是很好的示例,我的意思是,我确实从阅读这些线程中学到了一些东西),关键点以参数类型为中心。也就是说,使用了一种中间方法,在该方法中您传递要作为第一个参数调用的函数,然后是参数的列表或向量。这是其中的一个简短片段:
template <typename Ret,typename...Arg>
Ret call (Ret (*func)(Arg...),std::list<boost::any> args)
{ /*...do stuff... */ }
int foo(int x,double y,const std::string& z,std::string& w)
{ /*...more stuff...*/ }
int main ()
{
std::list<boost::any> args;
args.push_back(1);
args.push_back(4.56);
const std::string yyy("abc");
std::string zzz("123");
args.push_back(std::cref(yyy));
args.push_back(std::ref(zzz));
call(foo,args);
}
最终使用这种方法,参数参数类型是通过非模板化函数定义(即上面示例中的函数 foo
)推导出来的。很酷。
但是,我不能这样做,因为 GetAList
方法的第二个参数是一个包含前向引用的参数包,它们是 r 值。我确实查看了这些方法的源代码(例如 GetAList
),并将选项值进一步转发到它们的实现中(通过 std::forward
)。
我确实考虑过使用中间方法,这些方法具有与给定方法的可用选项的所有可能排列匹配的参数列表,例如 GetAList
。然后这些中间方法将调用相应的方法,例如 GetAList
。这将适用于上述模型。
然而,随着给定方法的可用选项数量的增加,匹配方法的数量会急剧增加。也就是说,您最终可能会使用 25、100 或更多方法来涵盖所有选项排列。这几乎和试图用宏魔法解决这个问题一样丑陋。
因此,我试图避免整个“如果这些参数调用此方法,如果这些参数调用另一个,等等”类型的解决方案。相反,我一直希望找到某种类型的 C++ 语法魔杖,我可以挥动它来解决问题。到目前为止无济于事,因此发布了我的问题。
为了完成,对 GetAList
方法(及其兄弟)的典型调用如下所示:
ReaderType myReader = client->GetAList( someName,optionsNamespace::PostName(std::string("whatever") ),optionsNamespace::MaxInList(1) );
这是我直到运行时才知道的最后两个参数。因此我必须在方法调用期间动态创建它们。
最后,可能值得注意的是(不是真的,但为了完成)这里涉及的调用堆栈包含一个 C-to-C++ 桥,其中有关最终用户选择的选项的信息来到我以 C varargs
列表形式显示的堆栈部分。但是,该列表内容可能需要转码,因此我无法按原样使用该内容。因此,我预处理 varargs
内容,必要时进行转码,并将结果保存在“选项块”中,因为缺少更好的术语。然后在我也负责的堆栈的 C++ 部分中处理该选项块;这就是这种魔法需要发生的地方。我需要筛选选项块,确定选择了哪些选项,检索它们的值并使用这些值在适当的方法调用语句(例如 GetAList
)中创建选项对象。
读完后,我希望它是有道理的。
无论如何,我仍在继续试验,试图找到答案。但是,如果你们中有任何人有意见/反馈,我会很有兴趣阅读。
编辑
一些回复要求澄清我的问题。对任何不清楚的地方表示歉意。我的问题是:如何在运行时动态创建可以发送到具有可变参数模板的 C++ 函数的参数列表?
正如我所提到的,关于 GetAList
方法的典型用例场景是静态指定您想要的选项对象。这意味着您的代码使用“硬编码”的 arg 列表调用该方法调用的方法。像这样:
ReaderType myReader = client->GetAList( someName,// <--- This is option object #1
optionsNamespace::MaxInList(1) ); // <--- This is option object #2
我需要能够调用上面显示的 GetAList
方法,但是选项参数不能直接“硬编码”到上面显示的调用中(选项对象 #1 和 #2)。我必须在运行时根据最终用户选择的选项动态创建选项对象的参数列表。
最初我探索了矢量方法。所以我们的想法是筛选选项列表,创建选项对象并同时将其推送到向量的末尾。然后调用一个中间函数,将向量扩展为逗号分隔的参数列表,可以传递给 GetAList
。然而,这也存在问题。首要的是确定参数类型。正如我最初的解释中所述,我阅读的示例中的类型是通过检查要调用的结束函数(上面示例中的 foo
)推导出来的。但是,GetAList
没有可以从中推断出参数类型的参数列表,就像 foo
一样。所以我研究了如何使用 std::forward
将对象从中间函数转发到 GetAList
,但我也没有想出任何工作。
这是否提供了清晰度?如果没有,我可以再试一次。感谢您到目前为止的回复,我很感激!
解决方法
绝对不可能。
一个简短的问题:参数的数量在运行时是已知的,但函数调用应该在编译时知道它。怎么解决?
通常的解决方案是尝试构建所有可能的调用,然后选择一个(类似于您最初提到的 std::visit
)。但如果使用 boost::any
或 std::any
但您不知道它可能具有的所有类型,则它很糟糕,有时无法达到目的。
也许你会想到可变函数或虚拟函数。但它们都不能跨越运行时和编译时之间的差距。 可变参数,只需从参数或其他东西中获取一些附加信息,然后确定要做什么。如果某些东西依赖于 type,它仍然应该依赖一个大的 switch
语句,然后选择一个分支。
如果你真的想这样做,也许你必须做出一些妥协:
- 首先,您必须知道所有可能的参数类型,并为每个参数指定一个唯一 ID。
- 其次,构建从所有 ID 组合到所有可能函数调用的映射(记住使无效的函数调用可编译)。您可以使用
switch
语句(有时不够用)、虚函数(将 ID 映射到从基类派生的对象)或大映射表。 - 第三,也许您应该创建一些辅助类(例如
std::ref
)来保持值类别和引用的不变性。 - 最后,当您获得参数时,将它们适当地存储并获得 ID 组合,然后进行实际的函数调用。在这种情况下,您将获得不确定类型的返回值。如果它有用并且实际上是多态的,则还必须构建返回类型映射表,然后在实际使用位置使用大
switch
语句。此过程类似于使用函数类型void(std::any)
再次执行 1-2-3。
事实上,这类似于 std::variant
和 std::visit
所做的。因此您还可以创建 std::variant< *all possible types* >
的包装器,然后使用 std::visit
。