首先让我解释一下我想要完成的事情。我需要创建一个类型擦除的仿函数(使用模板和虚函数),它能够在我正在开发的RTOS的消息队列存储中“安置”一个新对象。这种“技巧”是必需的,因为我希望大多数消息队列的代码都是非模板化的,只有真正需要类型信息的部分才能实现为类型擦除的仿函数。这是一个嵌入式微控制器(*)的项目,所以请假设我不能用模板制作整个消息队列,因为在这样的环境中ROM空间不是无限的。
我已经有了可以“复制 - 构造”和“移动构造”对象到队列存储中的仿函数(用于“推送”操作)我还有一个可以将对象“交换”出队列的仿函数存储(用于“弹出”操作)。要拥有一套完整的集,我需要一个能够将对象“置身”到队列存储中的仿函数。
所以这是展示我正面临创建问题的最小例子。请注意,这是一个简化的场景,它没有显示很多锅炉板(没有类,没有继承等),但错误完全相同,因为根本原因也可能相同。另请注意,使用std::bind()
(或 NOT 使用动态分配的类似机制)对我的用例至关重要。
#include <functional>
template<typename T, typename... Args>
void emplacer(Args&&... args)
{
T value {std::forward<Args>(args)...};
}
template<typename T, typename... Args>
void emplace(Args&&... args)
{
auto boundFunction = std::bind(emplacer<T, Args...>,
std::forward<Args>(args)...);
boundFunction();
}
int main()
{
int i = 42;
emplace<int>(i); // <---- works fine
emplace<int>(42); // <---- doesn't work...
}
在具有g++ -std=c++11 test.cpp
的PC上编译时,第一个实例化(使用变量的实例)编译没有问题,但第二个(直接使用常量42
)会抛出此错误消息:< / p>
test.cpp: In instantiation of ‘void emplace(Args&& ...) [with T = int; Args = {int}]’:
test.cpp:21:17: required from here
test.cpp:13:16: error: no match for call to ‘(std::_Bind<void (*(int))(int&&)>) ()’
boundFunction();
^
In file included from test.cpp:1:0:
/usr/include/c++/4.9.2/functional:1248:11: note: candidates are:
class _Bind<_Functor(_Bound_args...)>
^
/usr/include/c++/4.9.2/functional:1319:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int&&); _Bound_args = {int}]
operator()(_Args&&... __args)
^
/usr/include/c++/4.9.2/functional:1319:2: note: template argument deduction/substitution failed:
/usr/include/c++/4.9.2/functional:1315:37: error: cannot bind ‘int’ lvalue to ‘int&&’
= decltype( std::declval<_Functor>()(
^
/usr/include/c++/4.9.2/functional:1333:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) const [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int&&); _Bound_args = {int}]
operator()(_Args&&... __args) const
^
/usr/include/c++/4.9.2/functional:1333:2: note: template argument deduction/substitution failed:
/usr/include/c++/4.9.2/functional:1329:53: error: invalid initialization of reference of type ‘int&&’ from expression of type ‘const int’
typename add_const<_Functor>::type>::type>()(
^
/usr/include/c++/4.9.2/functional:1347:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) volatile [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int&&); _Bound_args = {int}]
operator()(_Args&&... __args) volatile
^
/usr/include/c++/4.9.2/functional:1347:2: note: template argument deduction/substitution failed:
/usr/include/c++/4.9.2/functional:1343:70: error: invalid initialization of reference of type ‘int&&’ from expression of type ‘volatile int’
typename add_volatile<_Functor>::type>::type>()(
^
/usr/include/c++/4.9.2/functional:1361:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) const volatile [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int&&); _Bound_args = {int}]
operator()(_Args&&... __args) const volatile
^
/usr/include/c++/4.9.2/functional:1361:2: note: template argument deduction/substitution failed:
/usr/include/c++/4.9.2/functional:1357:64: error: invalid initialization of reference of type ‘int&&’ from expression of type ‘const volatile int’
typename add_cv<_Functor>::type>::type>()(
我尝试在其他地方寻找灵感,但英特尔的TBB库有类似的代码(concurent_queue
)具有类似的功能(有emplace
功能)实际上根本没有任何安慰 - 它构造该对象立即“移动”到队列中......
知道上面的代码有什么问题吗?我想它确实很小,但我自己也解决不了......
答案 0 :(得分:2)
您已经解释了std::bind
如何工作(它将所有内容转换为左值),以及使用lambda。然而,这并非完全无足轻重。 Lambda可以按值或通过引用捕获。你需要混合使用两者:rvalue引用应该被假定为可能引用临时值,因此应该通过移动语义按值捕获。 (注意:这确实意味着原始对象在调用lambda之前从移动。)左值引用应该通过引用捕获,原因可能是显而易见的。
使这项工作的一种方法是手动将捕获的参数放在tuple
左值引用类型和非引用类型中,并在想要调用函数时解压缩:
template <typename T>
struct remove_rvalue_reference {
typedef T type;
};
template <typename T>
struct remove_rvalue_reference<T &&> {
typedef T type;
};
template <typename T>
using remove_rvalue_reference_t = typename remove_rvalue_reference<T>::type;
template <typename F, typename...T, std::size_t...I>
decltype(auto) invoke_helper(F&&f, std::tuple<T...>&&t,
std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::move(t))...);
}
template <typename F, typename...T>
decltype(auto) invoke(F&&f, std::tuple<T...>&&t) {
return invoke_helper<F, T...>(std::forward<F>(f), std::move(t),
std::make_index_sequence<sizeof...(T)>());
}
template<typename T, typename... Args>
void emplacer(Args&&... args) {
T{std::forward<Args>(args)...};
}
template<typename T, typename...Args>
void emplace(Args&&...args)
{
auto boundFunction =
[args=std::tuple<remove_rvalue_reference_t<Args>...>{
std::forward<Args>(args)...}]() mutable {
invoke(emplacer<T, Args...>, std::move(args));
};
boundFunction();
}
使用args emplace
调用T1 &, T2 &&
时,会在tuple<T1 &, T2>
中捕获args。在最终调用函数时,元组得到解压缩(thanks to @Johannes Schaub - litb的基本思想)。
lambda需要是可变的,以允许在调用函数时移动捕获的元组。
这使用了几个C ++ 14功能。其中大部分都可以避免,但我没有看到如何在捕获列表中指定初始化器的情况下如何做到这一点:C ++ 11 lambdas只能通过引用捕获(这将引用局部变量),或按价值(可以制作副本)。在C ++ 11中,我认为这意味着唯一的方法是不使用lambda,但有效地重新创建std::bind
的大部分内容。
答案 1 :(得分:1)
要扩展@ T.C。的评论,您可以通过更改已创建的emplacer
的类型来使代码生效。
auto boundFunction = std::bind(emplacer<T, Args&...>,
std::forward<Args>(args)...);
在&
之后注意Args
。原因是您将右值传递给emplace
函数,后者又创建了emplacer(int&&)
。然而std::bind
总是传递一个左值(因为它来自它的内部)。随着位置的变化,签名将更改为emplacer(int&)
(在引用折叠之后),它可以绑定到左值。