【问题标题】:Wrapper for template callback?模板回调的包装器?
【发布时间】:2017-07-09 17:30:01
【问题描述】:

立即回调

考虑以下示例:

template <typename lambda> void call_me_back(const lambda & callback)
{
    // Very complicated calculation to find the meaning of everything

    callback(42);
}

int main()
{
    call_me_back([](const int & value)
    {
        std :: cout << value << std :: endl;
    });
}

在这里,我为call_me_back 提供了一个接受int 的lambda。 call_me_back 在经过长时间的计算后调用 callback,然后 callback 将其打印出来。像蛋糕一样简单。

现在,假设根据执行情况,call_me_back 需要使用 int 或其他类型调用 callback。我们可以将前面的示例编辑为

template <typename lambda> void call_me_back(const lambda & callback)
{
    // Very complicated calculation to find the meaning of everything

    if(rand() % 2)
        callback(42);
    else
        callback("Kill all the humans.");
}

int main()
{
    call_me_back([](const auto & value)
    {
        std :: cout << value << std :: endl;
    });
}

到目前为止一切顺利。现在callback 可以做各种花样并根据其类型处理value

延迟回调

现在,回到第一个例子。假设call_me_back 尚未准备好立即使用int 调用callback。然而,它可以做的是将callback 存储到某个地方,然后再调用它。

例如:

std :: function <void(const int &)> the_callback;

template <typename lambda> void call_me_back(const lambda & callback)
{
    the_callback = callback;
}

void ready_to_answer()
{
    the_callback(42);
}

int main()
{
    call_me_back([](const auto & value)
    {
        std :: cout << value << std :: endl;
    });
}

现在,call_me_back 不是立即调用callback,而是将callback 存储在std :: function &lt;void(const int &amp;)&gt; 对象中(我知道,它在全局范围内,请耐心等待)。然后可能会发生很多事情,并且在某些时候有人可以调用ready_to_answer,它会检索先前存储的回调并调用它。例如,ready_to_answer 可以被另一个线程调用,但是有很多原因需要这样的范例,回调可以存储并在以后调用。

问题

如果我想实现第二个示例,但回调有延迟怎么办?。我似乎无法理解这一点。

我可以想象std :: function 是使用接受某些特定类型的虚拟调用运算符实现的。 std :: function 然后包装一个指向模板包装类的指针/引用,该模板包装类存储实际的 lambda 并通过将其参数转发到它正在存储的 lambda 来实现调用运算符。好,易于。 但我不能有模板虚方法!

我尝试过想出各种解决方案,但找不到任何可行的方法。这真的不可能吗?是否不可能有一些外部提供的 lambda 接受存储在某处的 const auto &amp; 参数,然后再调用它?

【问题讨论】:

    标签: c++ templates lambda auto generic-lambda


    【解决方案1】:

    你是对的,无限类型的集合是不可能的,但是如果你提前知道所有类型,你就可以做到:

    std :: function <void(const std :: variant<int, std :: string> &)> the_callback;
    
    template <typename lambda> void call_me_back(const lambda & callback)
    {
        the_callback = [callback](const auto & arg)
        {
            std :: visit(callback, arg);
        };
    }
    
    template <typename T> void ready_to_answer(const T & x)
    {
        the_callback(x);
    }
    
    int main()
    {
        call_me_back([](const auto & value)
        {
            std :: cout << value << std :: endl;
        });
    
        if (std :: rand() % 2)
        {
            ready_to_answer(42);
        }
        else
        {
            ready_to_answer("Hi!");
        }
    }
    

    【讨论】:

    • @PasserBy 我很好。
    【解决方案2】:

    在第二个示例中,您没有传递函数,而是使用两个方法 operator()(int)operator(char const (&amp;)[..]) 传递对匿名 lambda 对象的引用。因此,要存储这种回调,您需要复制 lambda 对象或存储特定的回调方法,即使用多个具有相应签名的 ::std::function。实际上为这两种情况显式传递两个回调会更清楚。

    std::function< void (int) > delalyed_int_cb;
    std::function< void (const char *) > delalyed_str_cb;
    
    template< typename callable_taking_int, typename callable_taking_string > void
    call_me_back(callable_taking_int && int_cb, callable_taking_string && str_cb)
    {
        delalyed_int_cb = std::forward< callable_taking_int >(int_cb);
        delalyed_str_cb = std::forward< callable_taking_str >(str_cb);
        ...
    }
    
    void
    ready_to_answer()
    {
        if(rand() % 2)
        {
            delalyed_int_cb(42);
        }
        else
        {
            delalyed_str_cb("Kill all the humans.");
        }
    }
    

    【讨论】:

      【解决方案3】:

      您可以实现一个为您存储 lambda 的类模板。

      #include<iostream>
      
      template<typename L>
      struct Telephone
      {
          L l;
      
          Telephone(L l) : l{std::move(l)} {}
      
          template<typename... Args>
          decltype(auto) ready_to_answer(Args&&... args)
          {
              return l(std::forward<Args>(args)...);   
          }
      };
      
      template<typename L>
      auto call_me_back(L&& l)
      {
          return Telephone<std::decay_t<L>>(std::forward<L>(l));
      }
      
      int main()
      {
          auto telephone = call_me_back([](auto x){ std::cout << x << std::endl; });
          telephone.ready_to_answer(42);
          telephone.ready_to_answer("Kill all the humans.");
      }
      

      这有一个缺点,Telephone 在 lambda 上被模板化,并且每个 lambda 的类型都不同。

      如果您事先知道函数签名是什么样的,您可以让Telephone 继承自一个公共基类并拥有虚拟的非模板方法。

      Telephone 进行一些自动构建和管理,您基本上实现了另一个std::function,但带有自定义签名

      struct Phone
      {
          // all Telephone<L> inherits from Phone and have these methods
      
          virtual void ready_to_answer(int) = 0;
          virtual void ready_to_answer(const char*) = 0;
      };
      
      struct Spouse
      {
          std::unique_ptr<Phone> phone;
      
          template<typename L>
          Spouse(L&& l) : phone{ new Telephone<std::decay_t<L>>{std::forward<L>(l)} } {}
      
          void ready_to_answer(int i) { phone->ready_to_answer(i); }
          void ready_to_answer(const char* str) { phone->ready_to_answer(str); }
      };
      

      编辑:我继续实现了std::function 的增强版本,它接受任意数量的签名,因此您可以编写

      function<void (int), void (std::string)> callback =
          [](auto x){std::cout << x << std::endl;};
      
      callback(42);
      callback("Kill all humans!");
      

      Live

      可以说实现不是微不足道的,这里解释的有点太多了。

      【讨论】:

        猜你喜欢
        • 2013-11-30
        • 2021-12-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多