【发布时间】: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&&...){}) 作为返回类型,因为这看起来更自然。但它会使编译器崩溃。)
解决方法也有点奇怪。也许我应该将此作为一个单独的问题提出,因为它主要与完美转发部分有关,但是:
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 似乎没问题...我不确定 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<T>(或const& 或&&),它似乎工作。可能还有一些额外的可以使这个透明的魔法,但我真的不需要它所以我没有调查。)
【问题讨论】:
-
嗯,出现没有展开的包?
-
@Columbo 我不确定你指的是哪一部分。
-
[weak, params]() {…} -
@Columbo 看看下面的文字。我尝试了几种变体,包括尝试扩展。
-
member也未被捕获。
标签: c++ c++11 lambda variadic-templates perfect-forwarding