为什么 std::bind() 在这种情况下不适用于常量?

Why std::bind() doesn't work with constants in this scenario?

本文关键字:不适用 适用于 常量 这种情况下 std bind 为什么      更新时间:2023-10-16

让我先解释一下我想要完成的目标。我需要创建一个类型擦除的函子(使用模板和虚拟函数(,该函子能够将新对象"放置"到我正在开发的 RTOS 的消息队列存储中。这种"欺骗"是必需的,因为我希望消息队列的大部分代码都是非模板化的,只有真正需要将类型信息实现为此类类型擦除函子的部分。这是一个嵌入式微控制器(*(的项目,所以请假设我只是不能用模板制作整个消息队列,因为在这样的环境中ROM空间不是无限的。

我已经有可以将对象"复制构造"和"移动构造"到队列存储中的函子(用于">

推送"操作(,并且我还有一个函子可以将对象"交换"出队列的存储(用于"弹出"操作(。为了拥有一个完整的集合,我需要一个能够将对象"放置"到队列存储中的函子。

因此,这是展示我在创建它时面临的问题的最小示例。请注意,这是一个简化的方案,它没有显示太多样板(没有类,没有继承等(,但错误完全相同,因为根本原因可能也相同。另请注意,使用 std::bind()(或不使用动态分配的类似机制(对我的用例至关重要。

#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(会抛出以下错误消息:

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函数(实际上根本没有放置 - 它立即构造对象并只是将其"移动"到队列中......

知道上面的代码有什么问题吗?我想这是一件非常小的事情,但我自己无法解决......


(*( - https://github.com/DISTORTEC/distortos

您已经解释了std::bind的工作原理(它将所有内容都转换为左值(,并改用 lambda。然而,这并非微不足道。Lambda 可以按值或引用捕获。您需要两者的混合:应假定右值引用可能引用临时,因此应按值捕获,并具有移动语义。(注意:这确实意味着在调用 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 T1 &, T2 && 调用 emplace 时,参数将被捕获在一个tuple<T1 &, T2>中。元组在最终调用函数时被解压缩(感谢 @Johannes Schaub - 基本思想的 litb(。

lambda 必须是可变的,以允许在调用函数时从中移动捕获的元组。

这使用了多个 C++14 功能。其中大多数是可以避免的,但是如果没有能够在捕获列表中指定初始化器,我看不出如何做到这一点:C++11 lambda只能通过引用(这将是对局部变量的引用(或按值(这将创建副本(进行捕获。在 C++11 中,我认为这意味着唯一的方法不是使用 lambda,而是有效地重新创建大部分std::bind .

要扩展 @T.C. 的注释,您可以通过更改创建的emplacer的类型来使代码工作。

auto boundFunction = std::bind(emplacer<T, Args&...>,
        std::forward<Args>(args)...);

请注意 Args 之后的&。原因是您将右值传递给 emplace 函数,该函数反过来创建emplacer(int&&)。 然而,std::bind总是传递一个左值(因为它来自其内部(。更改到位后,签名将更改为 emplacer(int&)(引用折叠后(,这可以绑定到左值。

相关文章: