【问题标题】:Dynamically creating function parameters that are perfectly forwarded in C++动态创建在 C++ 中完美转发的函数参数
【发布时间】: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


【解决方案1】:

绝对不可能

一个简短的问题:参数的数量在运行时是已知的,但函数调用应该在编译时知道它。如何解决?

通常的解决方案是尝试构建所有可能的调用,然后选择一个(类似于std::visit),您首先提到了这一点。但如果使用boost::anystd::any 但您不知道它可能具有的所有类型,这很糟糕,有时无法达到目的。

也许你会想到可变函数虚函数。但它们都无法通过运行时和编译时之间的差距可变函数,只是从参数或其他东西中获取一些附加信息,然后确定要做什么。如果某些东西依赖于type,它仍然应该依赖于一个大的switch-statement,然后选择一个分支。

如果你真的想这样做,也许你必须做出一些妥协:

  1. 首先,您必须了解所有可能的参数类型,并为每个参数指定一个唯一的 ID。
  2. 其次,构建从所有 ID 组合到所有可能的函数调用的映射(请记住使无效的函数调用可编译)。您可以使用switch-statement(有时不够用)、虚函数(将 ID 映射到从基类派生的对象)或大映射表。
  3. 第三,也许您应该创建一些帮助类(例如std::ref)来保持值类别和引用的不变性。
  4. 最后,当您获取参数时,适当地存储它们并获取 ID 组合,然后执行实际的函数调用。在这种情况下,您将返回值作为不确定类型。如果它有用并且实际上是多态的,您还必须构建一个返回类型映射表,然后在实际使用位置使用一个大的switch-statement。此过程类似于再次使用函数类型void(std::any) 执行 1-2-3。

事实上,这类似于std::variantstd::visit 所做的。所以你也可以创建一个std::variant&lt; *all possible types* &gt; 的包装器,然后使用std::visit

【讨论】:

  • 嗨-好的,非常感谢您的反馈。我开始怀疑这是否有意义。我开始认为它没有。我正在研究的最后一个解决方案涉及 boost::variant 和 boost::visit (我们使用的是 C++14),但找不到让它内联工作的方法(我也不确定这是否有意义)。由于这些原因,我放弃了幽灵并开始了一个全可能排列的解决方案。丑陋,但它会工作。再次感谢您!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-15
  • 1970-01-01
  • 2019-02-04
  • 2016-07-22
  • 1970-01-01
相关资源
最近更新 更多