【发布时间】:2021-03-10 18:43:55
【问题描述】:
我遇到了一个问题,目前我还没有找到好的解决方案。
本质上,我需要将参数传递给 C++ 可变参数模板函数。该函数具有以下通用签名/形式:
template <typename... Options>
ReaderType GetAList(std::string const& name, Options&&... options)
{ /*... do stuff ...*/ }
我已经阅读了几篇关于完美转发、可变参数模板、动态参数列表等的 StackOverflow Q&As。特别是这些:
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
这在我的场景中不起作用,因为我只是不知道用户将从可用选项中选择哪些选项。所以我必须看看他们想要使用哪些选项并动态形成一个参数列表。
在动态参数生成的其他示例中(这些示例很好,我的意思是,我确实从阅读这些线程中学到了一些东西),关键是围绕参数类型。也就是说,正在使用一种中间方法,在该方法中,您将要调用的函数作为第一个参数传递,然后是参数的列表或向量。这是一个快速的sn-p:
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 方法的第二个参数是一个包含前向引用的参数包,即右值。我确实查看了这些方法的源代码(例如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,
optionsNamespace::PostName(std::string("whatever") ), // <--- This is option object #1
optionsNamespace::MaxInList(1) ); // <--- This is option object #2
我需要能够调用上面显示的GetAList 方法,但不能将选项参数直接“硬编码”到该调用中,如上所示(选项对象#1 和#2)。我必须在运行时根据最终用户选择的选项动态创建选项对象的参数列表。
最初我探索了向量方法。所以想法是筛选选项列表,创建选项对象并同时将其推送到向量的末尾。然后调用一个中间函数,将向量扩展为一个逗号分隔的参数列表,这些参数可以传递给GetAList。但是,这样做存在问题。第一个是确定参数类型。正如我最初的解释中所述,我读到的示例中的类型是通过检查要调用的 end 函数来推断的(上面示例中的 foo)。但是GetAList 没有一个参数列表,可以从中推断出参数类型,就像foo 一样。所以我研究了如何使用std::forward 将对象从中间函数转发到GetAList,但我想出的也没有。
这是否提供了清晰性?如果没有,我可以再试一次。感谢您迄今为止的回复,我很感激!
【问题讨论】:
-
不清楚你想要什么,你能展示你想要的用法(使用使列出的被拒绝解决方案无效的变体)吗?
-
您正在从事的这个项目似乎描述得很好,但我认为这里没有提出明确的问题。这似乎更像是一个提出建议的邀请。
标签: c++ templates variadic-templates variadic-functions perfect-forwarding