【问题标题】:C++11 lambdas and parameter packsC++11 lambda 和参数包
【发布时间】:2016-06-16 02:05:18
【问题描述】:

我遇到了与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。如果我尝试params...,我会得到C3521: 'params' is not a parameter pack。 )

这个想法当然是返回一个函数,当调用该函数时,该函数将带有任意参数的成员函数发布到仅接受 void() 任务的工作队列,并且仅在尚未执行时保留对队列和任务的弱引用.

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

(顺便说一句,我最初尝试使用 decltype([](Params&amp;&amp;...){}) 作为返回类型,因为这看起来更自然。但它会使编译器崩溃。)


解决方法也有点奇怪。也许我应该将此作为一个单独的问题提出,因为它主要与完美转发部分有关,但是:

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&amp;) 方法)时,我收到一个绑定错误,即tribool 参数与tribool&amp;&amp; 参数不兼容。

(具体为:C2664: 'void WeakCaller&lt;T,boost::logic::tribool,boost::logic::tribool,const std::string &amp;&gt;::operator ()(boost::logic::tribool &amp;&amp;,boost::logic::tribool &amp;&amp;,const std::string &amp;)' : cannot convert argument 1 from 'boost::logic::tribool' to 'boost::logic::tribool &amp;&amp;'.)

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

我可以通过将WeakCaller 改为void operator()(Params... params) 来编译它,但这不会破坏完美转发吗? (奇怪的是,将 &amp;&amp; 留在顶级 lambda 似乎没问题...我不确定 std::function 签名和 lambda 签名之间的不匹配是否正常。)


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

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&lt;T&gt;(或const&amp;&amp;&amp;),它似乎工作。可能还有一些额外的可以使这个透明的魔法,但我真的不需要它所以我没有调查。)

【问题讨论】:

  • 嗯,出现没有展开的包?
  • @Columbo 我不确定你指的是哪一部分。
  • [weak, params]() {…}
  • @Columbo 看看下面的文字。我尝试了几种变体,包括尝试扩展。
  • member 也未被捕获。

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


【解决方案1】:

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

不,右值引用和转发引用是两个不同的东西。它们都涉及&amp;&amp;,但它们的行为不同。转发引用仅在存在模板参数推导(或auto 类型推导,使用相同规则)时发生。

这是一个转发参考:

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

模板参数推导会让T为任意类型;如果T 是左值引用,则v 是左值引用,否则v 是右值引用,因为引用折叠。

这不是转发参考:

void foo(int&& v);

这里没有模板参数推导,所以v是对int的纯右值引用,它只能绑定到右值。

这也不是转发参考:

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

再次因为没有模板参数推导。函数Foo&lt;int&gt;::bar 不是模板,它是一个普通函数,带有一个普通的右值引用参数,它只能绑定到右值。函数Foo&lt;int&amp;&gt;::bar 是一个普通函数,采用普通左值引用参数,该参数只能绑定到左值。

顺便说一句,我最初尝试使用decltype([](Params&amp;&amp;...){}) 作为返回类型,因为这看起来更自然。

那永远行不通。每个 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 的捕获集中。

【讨论】:

  • 谢谢,这看起来很有用。尽管将复制构造函数实现为移动构造函数似乎有点奇怪——这不是每个人都讨厌auto_ptr 的事情吗?另外,我尝试使用std::index_sequencethis implementation,但它会导致内部编译器错误。你知道更好的吗?
  • 好的,我发现another one 似乎有效。我必须对上述内容进行一些调整(主要是在几个地方添加const),它确实可以编译并适用于可复制类型。但是,对于仅移动类型,它与基于bind 的问题相同。我将在问题中添加我正在使用的仅移动测试用例,因为在那里格式化它更容易。也许我在做一些愚蠢的事情?
  • 没关系,我解决了。添加const 之后(这样MoveTupleWrapper 实际上有一个复制构造函数,而不仅仅是一个引用构造函数),std::move 仍然最终调用了元组的复制构造函数,因为const tuple&amp;&amp; 可以传递给const tuple&amp; 和不能传递给tuple&amp;&amp;。 (如果这会产生一个编译器警告,那就太好了,所以这会更明显。)让m_tuple 成员mutable 修复了这个问题,所以我现在有了一个完全有效的解决方案,谢谢!
  • 我注意到与autoptr 的相似之处。你当然是正确的,它有点邪恶,但这就是 VS2013 强加给我们的。我进行了编辑,将 mutable 添加到 lambda(不是元组),这可能是更好的解决方法。复制 ctor 和运算符中缺少 const 是有效且有意的。但是像您所做的那样添加constmutable 也会起作用,它稍微掩盖了复制ctor 和操作员将修改other(通过从其m_tuple 成员移动)这一事实。
  • 从复制构造函数中省略 const 会导致编译器错误 MoveTupleWrapper&lt;&gt;' : no copy constructor available or copy constructor is declared 'explicit',即使在 lambda 上使用 mutable。我将上面的当前版本添加到问题的末尾。它从一个 const 参数移动有点难看,但这只是一个内部细节,它确实编译并通过了测试。 (虽然不是左值引用,但这并不奇怪。)
【解决方案2】:

让事情顺利进行是我的目标。

我会攻击这个:

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) 替换为明确的 -&gt;decltype(expression) 我认为。无论如何,这是我将在此处跳过的子问题。

【讨论】:

  • 考虑到目标编译器是 VS2013,它几乎不是 C++11 而不是 C++14,我不确定这是否有用。然而,绝对有趣。不过,我不确定它在实践中与我自己发布的解决方法有什么不同。
【解决方案3】:

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,它们会出现问题)。我不太确定为什么需要重新模板化运算符,但它似乎绕过了我在使用 &amp;&amp; 参数时遇到的编译器错误。

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

【讨论】:

  • 需要模板化的operator()才能获得完美转发。没有它,你的右值引用只是右值引用,而不是转发引用。
  • [=](Params&amp;&amp;... params)std::function&lt;void(Params&amp;&amp;...)&gt; 也是如此,只是将&amp;&amp; 添加到member 函数的参数类型并不会神奇地创建转发引用。
  • @Oktalist 那么你是说这个版本还是有问题,还是说没问题?
  • FWIW,进一步的测试表明,在 lambda 上使用 Params...(没有 &amp;&amp;std::forward)对于可复制类型来说已经足够了,但是这两个版本都不适用于仅移动类型。这令人失望但并不令人惊讶。我对它们目前的情况很满意,但如果有人愿意的话,我会很感激一个可移动的版本。
  • 对于 VS2013 这很困难。 Yakk 的回答应该有效。
猜你喜欢
  • 2012-12-25
  • 1970-01-01
  • 2012-05-30
  • 1970-01-01
  • 2013-03-29
  • 1970-01-01
  • 2016-04-21
  • 1970-01-01
  • 2021-11-09
相关资源
最近更新 更多