使用Variadic模板的困难 [英] Difficulties using Variadic Templates

查看:114
本文介绍了使用Variadic模板的困难的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在写一个网络相关的类。我的应用程式接收到以下形式的网路讯息:

[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 ...)>回电话;

template< class Head,class ... Tail>
void parseHelper(uint8_t * data)
{
if(sizeof(Head)== 1){
uint8_t val;
memcpy(& val,data,sizeof(Head));
//将下一个unset参数设置为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);
//将下一个unset参数设置为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);
//将下一个unset参数设置为val
的值callback = std :: bind(callback,val);
data + = sizeof(Head);
} else {
throw std :: invalid_argument(我们只支持1,2和4字节整数!
}

//对其余的参数重复
parseHelper< Tail ...>(data);
}

模板< class ... Empty>
void parseHelper(uint8_t * data)
{
//不做任何事情,终止递归的情况
}

template< class ... T>
void parse(utin8_t * data)
{
//将数据解析成T ...参数并将它们绑定到`callback'中
parseHelper< T ...> ;(数据);

//在这一点`callback`有所有参数绑定从`data`

//调用回调
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>>回调)
{
MessageHandler< arg& mh;

mh.size = 0;
//执行顺序未定义,但我们不关心
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)
{
//对解析的消息进行操作
}

void bar(uint32_t a)
{
//对解析的消息进行操作
}

int main()
{
//注册回调
addMessageHandler< uint16_t,uint8_t,uint16_t,uint32_t>(0,std :: bind(& foo));
addMessageHandler< uint32_t>(1,std :: bind(& bar));
...

//通过网络获取消息
uint8_t messageId = some_network_library.read.first_byte();
MessageHandler mh = myMap [messageId];
uint8_t * data = some_network_library.read.bytes(mh.size);
//解析并调用带有解析值的回调
mh.parse(data);

return 0;
}

在main中,我们注册回调消息id,然后通过网络,获得适当的MessageHandler,通过变量解析变量,将每个变量附加到回调绑定,当我们绑定了所有的东西 - 调用回调。



所以,关心我的事情:


  1. (或其他整数键基于结构值的数据结构与近似常量查找)其中值是一个模板结构,你想存储不同类型的结构体吗? (即存储在地图中的值不是齐次类型)。


  2. 我需要做什么 parse parseHelper 函数可以工作?




    • 可以在std ::函数上附加绑定值

    • parse 中调用回调后,如何解除绑定所有绑定值? (或在通话后自动解除绑定)


代码工作?



如果有人可以将我的伪代码修复成一个工作的代码,解释为什么我的代码不工作,它是如何是可固定的,但只是

解决方案


  1. 参数多态性(模板)不是包含多态性): MessageHandler< int> MessageHandler< float> 是不同的类型,用于其他(基础类)。所以不,你不能创建一个可以存储具有不同参数的 MessageHandler 的容器。

还要记住,静态类型也意味着知道声明的大小。这是不可能的,没有解决参数到他们的实际值。



所以没有。您不能有一个地图< key,MessageHandler< T ...>> ,而不实际指定T,其禁止使用多个值 T。 ..



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



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



,因此我们可以创建一个地图< key,Any>


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

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



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



在parseHelper函数的情况下,我认为它有多个问题:




  • head值,你不需要某种循环/递归调用吗?

  • 如果你做这样的循环,应该什么时候停止?

  • 我认为你应该使用lambdas,而不是std :: bind:在你的情况下,一切都是单形的,所以你需要使用指针和大小。 std :: bind会占用更多的内存/ CPU空间。

  • 不要调用回调它?我认为 callback 是用户定义的值?



要做的是反序列化来自网络的值集合,然后将这些值作为您的回调的参数。是正确的吗?



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


I'm writing a networking-related class. My application receives network messages of the form

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

My class allows its user to register a callback for a specific message id.

Since there are variety of different messages with different number of different data entries (data entries are restricted to uint8_t, uint16_t and uint32_t), I decided to use C++11's variadic templates to lessen the burden of repeated code.

Here is my pseudo-code of what I want to do (didn't compile it and doubt it compiles)

#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;
}

In main, we register callbacks for message ids, then receive a message over the network, get appropriate MessageHandler, parse data variable by variable, appending each of them to the callback bind, and when we have binded everything — call the callback.

So, things that concern me:

  1. Is it even possible to have a map (or some other integer-key struct-value based data-structure with approximately constant lookup) where the value is a template struct and you want to store structs of different type in it? (i.e. values stored in the map are not of homogeneous type).

  2. What do I need to make parse and parseHelper functions to work?

    • I'm not sure if you can append-bind values to std::function like that
    • After calling the callback in parse, how do I unbind all the bind values? (or they automatically unbind after the call?)

How do I make this code work?

It would be great if someone could fix my pseudo-code into a working one, explaining why my code wouldn't work and how it's fixable, but just explanations are very very helpful too!

解决方案

  1. parametric polymorphism (templates) is not inclusion polymorphism (inheritance): MessageHandler<int> and MessageHandler<float> are different types and don't share a common definition that can be used for the other (a "base" class). So no, you cannot create a container that can store MessageHandler with different parameters.

Keep also in mind that static typing also implies to know the size of declaration. Which is not possible without solving the parameters to their actual "values".

So no. You cannot have a map<key, MessageHandler<T...>> without actually specifying T, which forbids using multiple values for T....

To solve this problem, you can use a type eraser. We use this for example:

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

so we can create a map<key, Any>.

  1. If you want to have a variadic callback approach, you could have a look at our Signal class:

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

It uses variadic templates to call callbacks with the corresponding parameters as arguments.

In the case of your parseHelper function, I think it has multiple issues:

  • It will take only of the "head" value, don't you need some kind of loop/recursive call?
  • If you do such loop, when should it stop? You need both the pointer and the size of the "message" you're reading.
  • I think you should use lambdas instead of std::bind: everything is monomorphic in your case, so std::bind will take a lot more memory/CPU for nothing.
  • Don't you want to call callback instead of setting it? I thought callback was a user defined value?

I think what you want to do is to "deserialize" the set of values coming from the network and then pass those values as arguments of your callback. Is that correct?

If that's the case you can have a look at this: http://stackoverflow.com/a/1547118/4525791

这篇关于使用Variadic模板的困难的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆