【问题标题】:Can a C++11/14 variadic template iterate on arguments to a function?C++11/14 可变参数模板可以迭代函数的参数吗?
【发布时间】:2015-09-01 11:16:16
【问题描述】:

我正在使用可变参数模板来捕获 Isis2(一个原子多播库 (isis2.codeplex.com))中的静态类型信息。一些 Isis2 事件是通过 upcall 传递的。例如,如果您编写代码

Group g("myGroup");
g.Handlers[UDPATE] += [](string& name, Foo& f) { ... your code };
....
g.OrderedSend(UPDATE, "John Doe", new Foo(...));

然后在组 g 中接收到一个多播,其中包含一个字符串和一个 Foo 对象的更新,Isis2 将构造一个 Foo 对象的本地实例,然后使用适当的参数调用这个 lambda。

这是我的谜题。我有可变参数代码来扫描 OrderedSend 的参数,并且可以捕获构建我的消息所需的静态类型信息。我最终将一个一维参数数组传递给真正的 OrderedSend 方法,每个参数都有它的类型、一个指针或对数据或对象的安全引用,对于一个对象,还有一个编组方法的地址。但是要使用可变参数模板来扫描 lambda,我需要查看函数的“内部参数列表”,因为添加到处理程序向量的对象是 lambda:type_traits 方法只会说它是“函数”类型的对象。我从 lambda 的参数列表中寻找字符串和 Foo 类型。但据我所知,type_traits.h 缺少访问参数列表的任何内容。

GCC-11 特定的选项是解开 typeid 并解析结果字符串。但是是否有一个可变参数模板功能可以让我在编译时访问 lambda 的参数列表?

【问题讨论】:

  • 所以,行话。 “upcall”只是带有额外行话的“callback”? g.Handlers[UPDATE] 的类型是什么? Group 是你的类型,还是 Isis?什么是“真正的 OrderedSend”?您的示例代码中有一个 OrderedSend,它是真的还是假的?在上面的代码中,您在哪里“扫描 lambda”?我的意思是,你有你 调用 lambda 的类型,你为什么认为你需要 lambda 的签名?为什么你认为 lambda 的(?an?)参数列表会有所帮助?
  • 哦,你是Isis2的作者。所以我猜“你的代码”和“isis2 代码”之间的区别有点武断。但我浏览了 Isis2 的 .h 文件,它没有包含 Group 类型,也没有 OrderedSend 方法。 C# 代码库中似乎确实有一些,您是否正在尝试编写当前 C# 库的 C++ 版本,并且您上面的代码是您想要在 C++ 中工作的示例?这确实解释了new Foo 绑定到Foo& 的奇怪之处(指针和引用是不同的类型,string& 不会绑定到"hello")。 UPDATE 包含哪些信息?
  • 那么,通过UPDATE 发送的所有消息都必须具有相同的签名,对吧?并且所有听众都必须与该签名兼容?为什么要在使用点(消息生产和消费)推断签名,然后在它们不匹配时导致运行时错误,而不是在一个位置定义消息语法(例如,定义 UPDATE 的位置) ,如果发送者发送不兼容的类型,或者如果侦听器注册了不兼容的回调,则在使用点生成编译时错误?
  • @Yakk:只是一个回调。 G.Handlers 是 std::vector 的数组。我将它们声明为 (void*)() 因为我不知道人们将实际注册的类型。至于 OrderedSend,真正的方法需要一个参数向量及其类型。我使用可变参数 temp,ate 来生成它(在 C# 中,这就是你使用 varargs 得到的)
  • @Yakk:你是如何“查看 Isis2 的 .h 文件”的?我还没有发布它。有一个 C# 库,这是你的意思吗?是的,我将它翻译成 C++,目的是将它放到启用 RDMA 的 NIC 上。该卡无法运行 Mono,所以我需要从 C# 转移到 C++。无论如何,RDMA 想以光线速度进行内存-内存 DMA,而托管内存我显然做不到,这也主张在这个新版本中使用 C++...

标签: c++11 lambda variadic-templates isis2


【解决方案1】:
template<class Sig>
struct MessageName {
  std::string name;
  MessageName() = delete;
  MessageName( std::string o ):name(o) {}
  MessageName(MessageName&&)=default;
  MessageName(MessageName const&)=default;
  MessageName& operator=(MessageName&&)=default;
  MessageName& operator=(MessageName const&)=default;
};

// trait to determine if some args are compatible:
template<class Sig, class...Ts>
struct is_compatible : std::false_type {};
template<>
struct is_compatible<void()> : std::true_type {};

template<class A0, class...Args, class T0, class...Ts>
struct is_compatible<void(A0, Args...), T0, Ts...>:
  std::integral_constant<bool,
    std::is_convertible<T0, A0>::value
    && is_compatible< void(Args...), Ts... >::value
  >
{};
struct HandlerMap {
  template<class Sig>
  void add_handler(
    MessageName<Sig> msg,
    block_deduction< std::function<Sig> > handler
  )
  {
    // ...
  }
  template<class Sig, class...Ts>
  typename std::enable_if<is_compatible<Sig, Ts...>::value>::type
  send_message( MessageName<Sig> msg, Ts&&... ts )
  {
    // ...
  }
};

UPDATE 令牌的类型应为 MessageName。所有MessageNames必须声明与他们关联的签名。

MessageName< void(std::string const&, Foo const&) > UPDATE{"update"};

如上。

然后,当您添加处理程序时,对add_handler 的调用将根据所需的签名检查分配的函数,并为您提供std::function

同样,当您发送消息时,可以根据签名检查传递的类型。您甚至应该在函数体中将参数转换为每个签名的参数类型。

这将尽可能多的类型检查转移到编译时,这是很好的 C++ 风格。

【讨论】:

    【解决方案2】:

    不,这是不可能的。如果对象不是 lambda 而是具有重载的结构怎么办?还是一个polylambda?您不能假设函数对象只有一个签名 - 有很多方法可以获得多个签名。

    这是一个简单的例子:

    struct fun {
        int i;
        void operator()(int x) {}
        void operator()(float x) {} 
    };
    

    关于这个结构或它的任何参数,没有什么超级复杂或非 POD。

    【讨论】:

    • 好吧,实际上我无法处理这种情况,并且会给出错误消息:您可以放入消息中的任何内容都需要转换为 POD,然后转换回运行时形式,而那些超级复杂的对象不支持这种进出转换。大多数消息库都有类似的限制(如 Google ProtoNetbufs)。对于这些情况,我可以说“不能那样做”。但是,如果我们谈论的是字符串、int 或 Foo(并且 Foo 实现了序列化接口),为什么不能弄清楚呢?
    • 对象或其参数没有什么超级复杂的。它们都可能仍然是 POD,但仍然是不可能的。
    • 由于这些系统通过调用传递事件,添加到处理程序列表的对象是一个事件处理程序——一个可调用的方法。所以它绝对是一个 lambda。我通过精确匹配工作,而不是复杂的运行时多态匹配,即使在 C# 中我也会这样做,因为 C# 可能会更花哨。所以我在这里并不要求最终的反思。只是一种非常有限的反射形式,具有精确的类型匹配......
    • @KenBirman:你不知道它是一个 lambda ......一个可调用的方法可以是任何具有适当 operator() 重载的结构。 lambda 和任意可调用对象之间存在 no 语义差异。它们完全无法区分。 Lambda 只是语法糖,根本没有特殊的语义。
    • @Puppy 方法的文档可能会声明只支持 lambda,而不支持常规函数或其他可调用对象,即使它们没有被明确拒绝,甚至通常无法检测到。但是,正如您在回答中提到的那样,多态 lambda 是一个很好的观点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-29
    • 2012-08-14
    • 1970-01-01
    • 2016-09-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多