mercredi 2 mars 2016

C++11 lambdas and parameter packs

I'm having essentially the same problem as this question, however unfortunately the only posted answer there is now a dead link.

Specifically, using VS2013 Update 4, I'm trying to get the following code to compile, and it's not being cooperative:

template<typename T, typename... Params>
auto PostWeakTask(const boost::weak_ptr<IWorkQueue>& queue,
                  void (T::*member)(Params...),
                  const boost::weak_ptr<T>& weak)
    -> std::function<void (Params&&...)>
{
    return [queue, member, weak](Params&&... params)
    {
        if (auto qp = queue.lock())
        {
            qp->Post([weak, params]()
            {
                if (auto strong = weak.lock())
                {
                    ((*strong).*member)(std::forward<Params>(params)...);
                }
            });
        }
    };
}

(As written, the capture of params fails with C3481: 'params': lambda capture variable not found. If I try using implicit capture with = instead, it says C2065: 'params' : undeclared identifier. If I try params..., I get C3521: 'params' is not a parameter pack.)

The idea of course is to return a function that when called will post a member function with arbitrary parameters to a work queue that accepts void() tasks only, and to only keep weak references to the queue and task when not yet executed.

I don't think there's anything actually wrong in the code here, though. I think I've found a workaround using bind instead of a lambda, but oddly it only seems to work with std::function and not boost::function. (Using Boost 1.55. I suspect this might be pre-rvalue-ref support?)

(On a side note, I originally tried to use decltype([](Params&...){}) as the return type, as this seems more natural. But it crashes the compiler.)


The workaround is doing something a bit weird too. Maybe I should ask this as a separate question as it mostly relates to the perfect-forwarding part instead, but:

template<typename T, typename... Params>
auto PostWeakTask(const boost::weak_ptr<IWorkQueue>& queue,
                  void (T::*member)(Params...),
                  const boost::weak_ptr<T>& weak)
    -> std::function<void (Params...)>
{
    struct WeakCaller
    {
        typedef void (T::*member_type)(Params...);
        typedef boost::weak_ptr<T> weak_type;

        WeakCaller(member_type member, const weak_type& weak)
            : m_member(member), m_weak(weak) {}

        void operator()(Params&&... params)
        {
            if (auto strong = m_weak.lock())
            {
                ((*strong).*m_member)(std::forward<Params>(params)...);
            }
        }

    private:
        member_type m_member;
        weak_type m_weak;
    };

    return [=](Params&&... params)
    {
        if (auto qp = queue.lock())
        {
            qp->Post(std::bind(WeakCaller(member, weak),
                     std::forward<Params>(params)...));
        }
    }
}

This seems like it should work, but when I try calling it (with a void (tribool,tribool,const std::string&) method) I get a binding error that a tribool parameter is not compatible with a tribool&& one.

(Specifically: C2664: 'void WeakCaller<T,boost::logic::tribool,boost::logic::tribool,const std::string &>::operator ()(boost::logic::tribool &&,boost::logic::tribool &&,const std::string &)' : cannot convert argument 1 from 'boost::logic::tribool' to 'boost::logic::tribool &&'.)

I thought part of the point of using rvalue references this way was that they were supposed to forward perfectly without needing multiple overloads?

I can make it compile by making WeakCaller have void operator()(Params... params) instead, but doesn't this defeat perfect forwarding? (Oddly it seems ok with leaving the && at the top-level lambda... and I'm not sure if the mismatch between the std::function signature and the lambda signature is ok.)

Aucun commentaire:

Enregistrer un commentaire