How to make unique lambda instances in C++? Or, how to set up id message handlers in xmpp with libstrophe?

271 views Asked by At

I'm using the C libstrophe library to make an xmpp application in C++11. I'm trying to register message handlers for specific IDs, so I can recognize a specific return message, using xmpp_id_handler_add.

void xmpp_id_handler_add(xmpp_conn_t * const conn,
                         xmpp_handler handler,
                         const char * const id,
                         void * const userdata)

But there's something about strophe's implementation of this that I don't understand.

Strophe will only accept a function pointer of the form

typedef int (*xmpp_handler)(xmpp_conn_t * const conn, 
                            xmpp_stanza_t * const stanza, 
                            void * const userdata);

That's easy enough to do with a static function, but looking through the source code I find this

    /* check if handler is already in the list */
    item = (xmpp_handlist_t *)hash_get(conn->id_handlers, id);
    while (item) {
        if (item->handler == (void *)handler)
            break;
        item = item->next;
    }
    if (item) return;

Meaning that if I try to call xmpp_id_handler_add twice with the same static function, but a different id and userdata, it will reject the second call.

So I thought that maybe I could make a lambda every time I want to add a new ID handler, like so

auto msgHandler = [](xmpp_conn_t* const pConn, 
                     xmpp_stanza_t* const pStanza, 
                     void* const pUserdata) -> int

But when I looked at the pointer value of the lambda

printf("%p\n", (void*)((xmpp_handler)msgHandler));

And ran it twice, I got the same value both times. It seems the lambda is just like a static function in this case.

So, how can I make a new, unique function pointer every time I want to listen for a new ID? Alternately, am I misunderstanding how libstrophe is supposed to be used? Are you supposed to have a new static function for each new ID you want to listen for?

3

There are 3 answers

0
Dmitry Podgorny On BEST ANSWER

Same problem has already been mentioned in the libstrophe issue tracker: https://github.com/strophe/libstrophe/issues/97. There is a patch in separated branch which is going to be merged to the master branch. Therefore, minor release 0.9.2 will contain it. The patch allows to add unique pairs of handler plus userdata instead of only handler.

1
W.F. On

If you are using c++14 you could create a generic lambda returning unique lambda (or rather lambda converted to static function) each time you call it:

auto msgHandlerCreator = [](auto X){ return +[](xmpp_conn_t* const pConn, 
                 xmpp_stanza_t* const pStanza, 
                 void* const pUserdata) -> int {/*...*/}; };

and call it each time with a different std::integral_constant e.g.:

msgHandlerCreator(std::integral_constant<std::size_t, 0>{})
msgHandlerCreator(std::integral_constant<std::size_t, 1>{})
0
W.F. On

In case of c++11 it won't be as easy to allow lambda to be declared inline but let's give it a try. We could create some helper struct to wrap our lambda but as unary + operator isn't constexpr (lambda has not linkage) we need to do some workarounds...:

template <class... Args>
struct voider {
    using type = void;
};

template <std::size_t, class, class = void>
struct FunctionWrapper;

template <std::size_t N, class L>
struct FunctionWrapper<N, L, typename voider<decltype(&L::operator())>::type>: FunctionWrapper<N, decltype(&L::operator())> {};

template <std::size_t N, class Res, class L, class... Args>
struct FunctionWrapper<N, Res (L::*)(Args...) const, void> {
    static Res (*l)(Args...);
    static Res foo(Args... args) {
        return l(args...);
    }
};

template <std::size_t N, class Res, class L, class... Args>
Res (* FunctionWrapper<N, Res (L::*)(Args...) const, void>::l)(Args...);

FunctionWrapper should now provide unique static function for each pair std::size_t x Lambda. Now function that will do actual wrapping:

template <std::size_t N, class L>
decltype(&FunctionWrapper<N, L>::foo) wrap(L &l) {
    FunctionWrapper<N, L>::l = +l;
    return &FunctionWrapper<N, L>::foo;
}

And we can enjoy unique function pointer for passed lambda as long as we provide unique id as a value known at compile time for each wrap call:

auto lambda = [](int x){std::cout << x << std::endl;};
std::cout << (void *)wrap<0>(lambda) << std::endl;
std::cout << (void *)wrap<1>(lambda) << std::endl;
wrap<0>(lambda)(1);
wrap<1>(lambda)(1);

Exemplary output:

0x400f28
0x400f76
1
1

[live demo]