使用Variadic模板的困难

时间:2015-02-14 03:22:38

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

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

的网络消息

[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中调用回调后,如何取消绑定所有绑定值? (或者他们在通话后自动取消绑定?)
  3. 如何使此代码生效?

    如果有人可以将我的伪代码修复为工作代码,解释为什么我的代码不起作用以及它是如何修复的,那将是很好的,但只是解释也非常有用!

2 个答案:

答案 0 :(得分:1)

  1. 参数多态(模板)不是包含多态(继承):MessageHandler<int>MessageHandler<float>是不同的类型,不共享可用于另一个的通用定义(“基础”类)。所以不,你不能创建一个可以存储MessageHandler不同参数的容器。
  2. 还要记住,静态类型也意味着知道声明的大小。如果不将参数解析为实际的“值”,这是不可能的。

    所以没有。如果没有实际指定T,则不能有map<key, MessageHandler<T...>>,这禁止使用T...的多个值。

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

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

    所以我们可以创建一个map<key, Any>

    1. 如果你想要一个可变回调方法,你可以看看我们的Signal类:
    2. 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

答案 1 :(得分:0)

您可以通过模板参数轻松解析内存中的动态数据(请参阅第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)
    };
};

所以这里是带有一些注释的代码

  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
    
  6. 当然它只适用于POD类型。我没有使用uint8_tuint16_tuint32_t,但它们没有问题。