【问题标题】:Difficulties using Variadic Templates使用可变参数模板的困难
【发布时间】:2015-02-14 03:22:38
【问题描述】:

我正在编写一个与网络相关的课程。我的应用程序接收表单的网络消息

[uint8_t message id, uint8_t/uint16_t/uint32_t data ...]

我的类允许其用户为特定的消息 ID 注册回调。

由于不同的消息有多种不同的数据条目数量(数据条目仅限于 uint8_t、uint16_t 和 uint32_t),因此我决定使用 C++11 的可变参数模板来减轻重复代码的负担。

这是我想要做的伪代码(没有编译它并且怀疑它编译)

#include <arpa/inet.h>
#include <stdexcept>

using namespace std;

template<class ...T>
struct MessageHandler {
    size_t size;
    std::function<void(T...)> callback;

    template<class Head, class... Tail>
    void parseHelper(uint8_t *data)
    {
        if (sizeof(Head) == 1) {
            uint8_t val;
            memcpy(&val, data, sizeof(Head));
            // set next unset argument to the value of val
            callback = std::bind(callback, val);
            data += sizeof(Head);
        } else if (sizeof(Head) == 2) {
            uint16_t val;
            memcpy(&val, data, sizeof(Head));
            val = ntohs(val);
            // set next unset argument to the value of val
            callback = std::bind(callback, val);
            data += sizeof(Head);
        } else if (sizeof(Head) == 4) {
            uint32_t val;
            memcpy(&val, data, sizeof(Head));
            val = ntohl(val);
            // set next unset argument to the value of val
            callback = std::bind(callback, val);
            data += sizeof(Head);
        } else {
            throw std::invalid_argument("We support only 1, 2 and 4 byte integers!");
        }

        // repeat for the rest of arguments
        parseHelper<Tail...>(data);
    }

    template<class ...Empty>
    void parseHelper(uint8_t *data)
    {
        // do nothing, terminating case of recursion
    }

    template<class ...T>
    void parse(utin8_t *data)
    {
        // parse `data` into T... arguments and bind them into `callback`
        parseHelper<T...>(data);

        // at this point `callback` has all arguments binded from `data`

        // invoke the callback
        callback();
    }
}

// <message id, callback-holding helper struct>
std::unordered_map<uint8_t, MessageHandler> myMap;

template<class...T>
void dummy(T&&...)
{
    // a dummy, does nothing
}

template<class...T>
void addMessageHandler(uint8_t messageId, std::function<void<T... arg>> callback)
{
    MessageHandler<arg> mh;

    mh.size = 0;
    // order of execution is undefined, but we don't care
    dummy( (mh.size += sizeof(arg))... );

    mh.callback = callback;

    myMap[messageId] = mh;
}

void foo(uint16_t a, uint8_t b, uint16_t c, uint32_t d)
{
    // do stuff with the parsed message
}

void bar(uint32_t a)
{
    // do stuff with the parsed message
}

int main()
{
    // register callbacks
    addMessageHandler<uint16_t, uint8_t, uint16_t, uint32_t>(0, std::bind(&foo));
    addMessageHandler<uint32_t>(1, std::bind(&bar));
    ...

    // get message over the network
    uint8_t messageId = some_network_library.read.first_byte();
    MessageHandler mh = myMap[messageId];
    uint8_t *data = some_network_library.read.bytes(mh.size);
    // parses and calls the callback with parsed values
    mh.parse(data);

    return 0;
}

在 main 中,我们为消息 ID 注册回调,然后通过网络接收消息,获取适当的 MessageHandler,逐个解析 data 变量,将它们中的每一个附加到回调绑定中,当我们绑定所有内容时 - 调用回调。

所以,我关心的事情:

  1. 是否甚至可以有一个映射(或其他一些基于整数键结构值的数据结构,具有近似恒定的查找),其中值是模板结构,并且您希望将不同类型的结构存储在它? (即存储在地图中的值不是同质类型的)。​​

  2. 我需要什么才能使parseparseHelper 函数正常工作?

    • 我不确定您是否可以像这样将值附加到 std::function
    • parse中调用回调后,如何解除绑定所有绑定值? (或者他们在通话后自动解除绑定?)

如何使这段代码工作?

如果有人能将我的伪代码修复成一个可以工作的伪代码,那就太好了,解释为什么我的代码不能工作以及它是如何修复的,但只是解释也非常有帮助!

【问题讨论】:

  • 您可以做一些事情...我看到 JSON 解析器正在做的事情是拥有指向每种类型的成员或指针,然后拥有一个枚举成员来告诉您哪种类型是有效类型...或者您可以对 (void *) 进行良好的旧 c 样式转换
  • 这究竟能解决什么问题?另外,我不希望代码知道有关特定类型的任何信息。用户在提供回调时定义类型,其余代码必须使用用户提供的任何类型。我将可能性限制为 1、2 和 4 字节整数类型只是为了简化解释和示例代码,但实际代码会有点不同,可能是任意类型。
  • @JohnBerger Boost.Fusion 结构为这个问题提供了一个非常优雅的解决方案。查看this 博客文章和此CppCon talk。但是,就目前而言,您的问题太广泛了。尽管如此:)

标签: c++ templates c++11 bind variadic-templates


【解决方案1】:
  1. 参数多态性(模板)不是包含多态性(继承):MessageHandler&lt;int&gt;MessageHandler&lt;float&gt; 是不同的类型,并且不共享可用于另一个的通用定义(“基”类)。所以不行,你不能创建一个可以存储 MessageHandler 不同参数的容器。

还要记住,静态类型也意味着知道声明的大小。如果不将参数求解为它们的实际“值”,这是不可能的。

所以没有。如果没有实际指定 T,就不能拥有 map&lt;key, MessageHandler&lt;T...&gt;&gt;,这禁止为 T... 使用多个值。

要解决此问题,您可以使用类型橡皮擦。我们以此为例:

https://github.com/aerys/minko/blob/master/framework/include/minko/Any.hpp

所以我们可以创建一个map&lt;key, Any&gt;

  1. 如果您想使用可变参数回调方法,可以查看我们的 Signal 类:

https://github.com/aerys/minko/blob/master/framework/include/minko/Signal.hpp

它使用可变参数模板以相应的参数作为参数调用回调。

就您的 parseHelper 函数而言,我认为它有多个问题:

  • 它只需要“he​​ad”值,您不需要某种循环/递归调用吗?
  • 如果你做这样的循环,它应该在什么时候停止?您需要指针和正在阅读的“消息”的大小。
  • 我认为您应该使用 lambdas 而不是 std::bind:在您的情况下,一切都是单态的,因此 std::bind 将占用更多的内存/CPU。
  • 您不想调用callback 而不是设置它吗?我以为callback 是用户定义的值?

我认为您想要做的是“反序列化”来自网络的一组值,然后将这些值作为回调的参数传递。对吗?

如果是这样,你可以看看这个:https://stackoverflow.com/a/1547118/4525791

【讨论】:

    【解决方案2】:

    您可以通过模板参数轻松解析内存中的动态数据(参见第 1 部分)。关于如何使用元组调用函数,answer 非常有用并且可以应用(参见第 2 部分)。现在您所需要的只是存储有关函数的信息并使用动态解析的值进行调用。同样从我的角度来看,在阅读消息时查找消息的大小可能存在另一个问题,所以我为它做了简单的助手struct

    template< typename T1, typename... T2 >
    struct size_of {
        enum {
            size = sizeof (T1) + size_of < T2... >::size
        };
    };
    
    template< typename T >
    struct size_of< T > {
        enum {
            size = sizeof (T)
        };
    };
    

    所以这里是一些 cmets 的代码

    1. 解析器

      template< typename T1, typename... T2 >
      struct parser {
          static std::tuple< T1, T2... > parse(void* data) {
              // get value from pointer
              T1* p = (T1*) data;
              std::cout << typeid (*p).name() << " " << *p << std::endl;
              // concatenate current value with next one 
              return std::tuple_cat(std::make_tuple(*p),
                      parser < T2... >::parse(p + 1));
          }
      };
      
      template< typename T1 >
      struct parser< T1 > {
          static std::tuple< T1 > parse(void* data) {
              T1* p = (T1*) data;
              std::cout << typeid (*p).name() << " " << *p << std::endl;
              return std::make_tuple(*p);
          }
      };
      

      您还可以重新实现此类以返回解析值的大小以确保一切正常。

    2. 函数调用

      // function call using tuple 
      template < int N >
      struct __apply_impl {
          template < typename... ArgsF, typename... ArgsT, typename... Args >
          static void apply(const std::function<void( ArgsF...)>& f,
                  const std::tuple<ArgsT...>& t,
                  Args... args) {
              __apply_impl < N - 1 > ::apply(f, t, std::get < N - 1 > (t), args...);
          }
      };
      
      template <>
      struct __apply_impl<0> {
          template < typename... ArgsF, typename... ArgsT, typename... Args >
          static void apply(const std::function<void( ArgsF...)>& f,
                  const std::tuple<ArgsT...>& /* t */,
                  Args... args) {
              // actual call
              f(args...);
          }
      };
      
      // wrapper function
      template < typename... ArgsF, typename... ArgsT >
      void call_with_tuple(const std::function<void( ArgsF...)>& f,
              std::tuple<ArgsT...> const& t) {
          __apply_impl<sizeof...(ArgsT)>::apply(f, t);
      }
      
    3. 消息调度程序或消息处理程序

      // message dispatcher
      class message_dispatcher {
      protected:
          // callback interface
          struct callback_t {
          public:
              virtual void call(void*) = 0;
          };
          // and implementation
          template< typename... Ty >
          struct callback_impl : public callback_t {
              typedef std::function< void(Ty...) > function_t;
      
              callback_impl(const function_t& f) {
                  m_f = f;
              }
              virtual void call(void* data) {
                  // parse to tuple
                  auto t = parser < Ty... >::parse(data);
                  // call function
                  call_with_tuple(m_f, t);
              }
              function_t m_f;
          };
      public:
          // process incoming data 
          void process(int t, void* data) {
              m_c[t]->call(data);
          }
          // register callback for type t
          template< typename... Ty >
          void add(int t, const std::function< void(Ty...) >& f) {
              m_c[t] = new callback_impl < Ty... >(f);
          }
      protected:
          std::map< int, callback_t* > m_c;
      };
      
    4. 例子

      void foo(int a, float b, char c) {
          std::cout << "in foo(int,float,char) with args: ";
          std::cout << "1: " << a << ", "
                  << "2: " << b << ", "
                  << "3: " << c << std::endl;
      }
      
      struct foo_t {
          void foo(int a, double b) {
              std::cout << "in foo_t::foo(int,double) with args: ";
              std::cout << "1: " << a << ", "
                      << "2: " << b << std::endl;
          }
      };
      
      int main(int argc, char** argv) {
          // pack data 
          char* b1 = new char[size_of< int, float, char >::size];
          int a1 = 1;
          float a2 = 2.;
          char a3 = 'a';
          memcpy(b1, &a1, sizeof (a1));
          memcpy(b1 + sizeof (a1), &a2, sizeof (a2));
          memcpy(b1 + sizeof (a1) + sizeof (a2), &a3, sizeof (a3));
      
          // pack data 
          char* b2 = new char[size_of< int, double >::size];
          int a4 = 10;
          double a5 = 20.;
          memcpy(b2, &a4, sizeof (a4));
          memcpy(b2 + sizeof (a4), &a5, sizeof (a5));
      
          // create callbacks
          std::function<void(int, float, char) > f1(&foo);
          foo_t foo;
          std::function<void(int, double) > f2 = std::bind(&foo_t::foo, &foo,
                  std::placeholders::_1,
                  std::placeholders::_2);
      
          message_dispatcher md;
          // register callbacks
          md.add(0, f1);
          md.add(1, f2);
          // call 
          md.process(0, b1);
          md.process(1, b2);
      
          return 0;
      }
      
    5. 输出

      i 1
      f 2
      c a
      in foo(int,float,char) with args: 1: 1, 2: 2, 3: a
      i 10
      d 20
      in foo_t::foo(int,double) with args: 1: 10, 2: 20
      

    当然,它只适用于 POD 类型。 uint8_tuint16_tuint32_t我没用过,但是不会有问题的。

    【讨论】:

      猜你喜欢
      • 2012-04-10
      • 2021-10-01
      • 2016-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-14
      • 1970-01-01
      • 2016-10-06
      相关资源
      最近更新 更多