C ++ 11 lambdas和参数包

时间:2016-03-02 23:41:16

标签: c++ c++11 lambda variadic-templates perfect-forwarding

我遇到与this question基本相同的问题,但不幸的是,现在唯一公布的答案是死链接。

具体来说,使用VS2013 Update 4,我正在尝试使用以下代码进行编译,并且它不合作:

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, member, params]()
            {
                if (auto strong = weak.lock())
                {
                    ((*strong).*member)(std::forward<Params>(params)...);
                }
            });
        }
    };
}

(正如所写的那样,params的捕获失败了C3481: 'params': lambda capture variable not found。如果我尝试使用=隐式捕获,它会说C2065: 'params' : undeclared identifier。如果我尝试{{} 1}},我得到params...。)

当然,我们的想法是返回一个函数,该函数在被调用时会将具有任意参数的成员函数发布到仅接受C3521: 'params' is not a parameter pack任务的工作队列,并且仅在保留时不保留对队列和任务的弱引用但已执行。

但是,我不认为这里的代码有任何错误。我想我找到了使用void()而不是lambda的解决方法,但奇怪的是它似乎只适用于bind而不是std::function。 (使用Boost 1.55。我怀疑这可能是pre-rvalue-ref支持?)

(另一方面,我最初尝试使用boost::function作为返回类型,因为这看起来更自然。但它会使编译器崩溃。)

解决方法是做一些有点奇怪的事情。也许我应该将此作为一个单独的问题,因为它主要涉及完美转发部分,但是:

decltype([](Params&&...){})

这似乎应该可行,但是当我尝试调用它时(使用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)...)); } } } 方法)我得到一个绑定错误,void (tribool,tribool,const std::string&)参数与tribool参数不兼容

(具体来说:tribool&&。)

我认为使用右值引用这一点的部分原因是它们应该完美地转发而不需要多次重载?

我可以通过让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 &&'改为WeakCaller来进行编译,但是这不能完美转发吗? (奇怪的是,将void operator()(Params... params)留在顶级lambda似乎没问题......而且我不确定&&签名和lambda签名之间的不匹配是否正常。)

仅供参考,这是我在调整Oktalist's answer之后现在使用的最终版本:

std::function

我猜它仍然不是真正的通用,因为它不适用于左值引用,但是因为这些在延迟回调上下文中是危险的,所以这并不重要。

(但是,如果您真的急于传递左值参考,那么如果您使用namespace detail { template<size_t... Ints> struct index_sequence { static size_t size() { return sizeof...(Ints); } }; template<size_t Start, typename Indices, size_t End> struct make_index_sequence_impl; template<size_t Start, size_t... Indices, size_t End> struct make_index_sequence_impl<Start, index_sequence<Indices...>, End> { typedef typename make_index_sequence_impl< Start + 1, index_sequence<Indices..., Start>, End>::type type; }; template<size_t End, size_t... Indices> struct make_index_sequence_impl<End, index_sequence<Indices...>, End> { typedef index_sequence<Indices...> type; }; template <size_t N> using make_index_sequence = typename make_index_sequence_impl<0, index_sequence<>, N>::type; template<typename... Ts> using index_sequence_for = make_index_sequence<sizeof...(Ts)>; template<typename... Ts> struct MoveTupleWrapper { MoveTupleWrapper(std::tuple<Ts...>&& tuple) : m_tuple(std::move(tuple)) {} MoveTupleWrapper(const MoveTupleWrapper& other) : m_tuple(std::move(other.m_tuple)) {} MoveTupleWrapper& operator=(const MoveTupleWrapper& other) { m_tuple = std::move(other.m_tuple); } template<typename T, typename... Params> void apply(void (T::*member)(Params...), T& object) const { applyHelper(member, object, index_sequence_for<Ts...>()); } template<typename T, typename... Params, size_t... Is> void applyHelper(void (T::*member)(Params...), T& object, index_sequence<Is...>) const { (object.*member)(std::move(std::get<Is>(m_tuple))...); } private: mutable std::tuple<Ts...> m_tuple; }; template<typename... Ts> auto MoveTuple(Ts&&... objects) -> MoveTupleWrapper<std::decay_t<Ts>...> { return std::make_tuple(std::forward<Ts>(objects)...); } template<typename T, typename... Params> struct WeakTaskPoster { WeakTaskPoster(const boost::weak_ptr<IWorkQueue>& queue, void (T::*member)(Params...), const boost::weak_ptr<T>& weak) : m_queue(queue), m_member(member), m_weak(weak) {} template<typename... XParams> void operator()(XParams&&... params) const { if (auto qp = m_queue.lock()) { auto weak = m_weak; auto member = m_member; auto tuple = MoveTuple(std::forward<XParams>(params)...); qp->Post([weak, member, tuple]() { if (auto strong = weak.lock()) { tuple.apply(member, *strong); } }); } } private: boost::weak_ptr<IWorkQueue> m_queue; void (T::*m_member)(Params...); boost::weak_ptr<T> m_weak; }; } template<typename T, typename... Params> auto PostWeakTask(const boost::weak_ptr<IWorkQueue>& queue, void (T::*member)(Params...), const boost::weak_ptr<T>& weak) -> detail::WeakTaskPoster<T, Params...> { return { queue, member, weak }; } 致电并以std::ref(x)(或std::reference_wrapper<T>或{{1可能还有一些额外的魔法可以使它变得透明,但我真的不需要它,所以我没有调查过。)

3 个答案:

答案 0 :(得分:2)

  

我认为使用右值引用这一点的部分原因是它们应该完美地转发而不需要多次重载?

不,rvalue引用和转发引用是两回事。它们都涉及&&,但它们的行为不同。转发引用仅在存在模板参数推导(或auto类型推导,使用相同规则)的情况下发生。

这是转发参考:

template<typename T>
void foo(T&& v);

模板参数推导将T为任何类型;如果T是左值参考,那么v是左值参考,否则v是左值参考,因为参考折叠。

这不是转发参考:

void foo(int&& v);

此处没有模板参数推断,因此v是对int的简单右值引用,它只能绑定到rvalues。

这也不是转发参考:

template<typename T>
struct Foo
{
    void bar(T&& v);
};

再次因为没有模板参数推论。函数Foo<int>::bar不是模板,它是一个普通函数,它采用一个简单的右值引用参数,它只能绑定到rvalues。函数Foo<int&>::bar是一个普通的函数,它带有一个普通的左值引用参数,它只能绑定到左值。

  

另一方面,我最初尝试使用decltype([](Params&&...){})作为返回类型,因为这看起来更自然。

这永远不会奏效。每个lambda表达式都表示一个唯一类型,因此decltype([]{})decltype([]{})是两种不同的,不相关的类型。

这是一个可能的解决方案。使用的唯一C ++ 14功能是std::index_sequence,您可以使用Google实现C ++ 11。

template<typename... Ts>
struct MoveTupleWrapper
{
    MoveTupleWrapper(std::tuple<Ts...>&& tuple) : m_tuple(tuple) {}
    MoveTupleWrapper(MoveTupleWrapper& other) : m_tuple(std::move(other.m_tuple)) {}
    MoveTupleWrapper& operator=(MoveTupleWrapper& other) { m_tuple = std::move(other.m_tuple); }
    std::tuple<Ts...> m_tuple;

    template<typename T, typename... Params>
    void apply(void (T::*member)(Params...), T& object)
    {
        applyHelper(member, object, std::index_sequence_for<Ts...>);
    }
    template<typename T, typename... Params, size_t... Is>
    void applyHelper(void (T::*member)(Params...), T& object, std::index_sequence<Is...>)
    {
        (object.*member)(std::move(std::get<Is>(m_tuple))...);
    }
};

template<typename... Ts>
auto MoveTuple(Ts&&... objects)
    -> MoveTupleWrapper<std::decay_t<Ts>...>
{
    return std::make_tuple(std::forward<Ts>(objects)...);
}

template<typename T, typename... Params>
struct WeakTaskPoster
{
    WeakTaskPoster(const boost::weak_ptr<IWorkQueue>& queue,
                   void (T::*member)(Params...),
                   const boost::weak_ptr<T>& weak) :
        m_queue(queue),
        m_member(member),
        m_weak(weak)
    {}
    boost::weak_ptr<IWorkQueue> m_queue;
    void (T::*m_member)(Params...);
    boost::weak_ptr<T> m_weak;

    template<typename... XParams>
    void operator()(XParams&&... params) const
    {
        if (auto qp = m_queue.lock())
        {
            auto weak = m_weak;
            auto member = m_member;
            auto tuple = MoveTuple(std::forward<XParams>(params)...);
            qp->Post([weak, member, tuple]() mutable
            {
                if (auto strong = weak.lock())
                {
                    tuple.apply(member, *strong);
                }
            });
        }
    }
};

template<typename T, typename... Params>
auto PostWeakTask(const boost::weak_ptr<IWorkQueue>& queue,
                  void (T::*member)(Params...),
                  const boost::weak_ptr<T>& weak)
    -> WeakTaskPoster<T, Params...>
{
    return { queue, member, weak };
}

请注意WeakTaskPoster::operator()是一个模板,因此我们获得了完美的转发。 C ++ 14做到这一点的方法就是普通的lambda。然后有这个MoveTuple的东西。这是一个围绕std::tuple的包装器,它只是根据移动来实现复制,所以我们可以解决缺少C ++ 14 lambda的移动问题。它还完全实现了必要的std::apply子集。

这与Yakk的答案非常相似,只是参数元组被移动而不是复制到lambda的捕获集中。<​​/ p>

答案 1 :(得分:1)

让工作成为我的目标。

我会攻击这个:

qp->Post([weak, member, params]()
{
  if (auto strong = weak.lock()) {
    ((*strong).*member)(std::forward<Params>(params)...);
  }
});

首先,让我们将params放入lambda。

auto tParams = std::make_tuple( std::forward<Params>(params)... );
qp->Post([weak, member, tParams]()
{
  if (auto strong = weak.lock()) {
    ((*strong).*member)(std::forward<Params>(params)...);
  }
});

然而,你会注意到现在身体没有编译!

然而,我们更接近。

template<class T, class M>
struct member_invoke_t;
template<class T, class R, class...Args>
struct member_invoke_t<T*, R(::*)(Args...)> {
  T* t;
  R(::*m)(Args...);
  template<class...Ts>
  R operator()(Ts&&...ts)const{
    return (t->*m)(std::forward<Ts>(ts)...);
  }
};
template<class T, class M>
member_invoke_t<T*, M*> member_invoke(T* t, M*m) {
  return {t, m};
};

然后我们只需要写std::apply

qp->Post([weak, member, tParams]()mutable
{
  if (auto strong = weak.lock())
    std::apply( member_invoke(strong.get(), member), std::move(tParams) );
}

应该是可行的。 (上面的代码假定传递给Post的lambda只会被调用一次 - 因此movemutable)。

std::apply有一个可能的C ++ 14实现over here。转换为C ++ 11需要编写index_sequence并使用明确的decltype(auto)替换->decltype(expression)。无论如何,这是一个子问题,我将跳过这里。

答案 2 :(得分:0)

Yakk's answer确实激励了我对上面发布的解决方法稍作修改:

template<typename T, typename... 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) {}

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

private:
    member_type m_member;
    weak_type m_weak;
};

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

这看起来像预期的那样编译和工作,虽然我还没有使用仅限移动类型进行测试(我怀疑它们会因使用bindfunction而出现问题)。我不确定为什么需要重新模板化运算符,但它似乎绕过了&&参数的编译器错误。

我还不确定PostWeakTask的返回类型应该是std::function<void(Params...)>还是std::function<void(Params&&...)>;似乎两种方式都可以编译好。