【问题标题】:How to pass first N args to a C++ function如何将前 N 个参数传递给 C++ 函数
【发布时间】:2017-10-16 17:39:04
【问题描述】:

我有一个这样的函数:

void loadData(std::function<void (std::string, std::string, std::string)> callback)
{
    // data loading stuff
    callback(body, subject, header);
}

问题是我不一定需要在我的回调函数中使用subjectheader。现在我是这样处理的:

loadData([](std::string body, std::string, std::string){
    std::cout << body;
})

我想换成

loadData([](std::string body){
    std::cout << body;
})

并自动将尽可能多的参数传递给回调函数。 我不想为所有 3 个可能的参数计数手动重载 loadData 函数。我也不想在调用站点上使用任何更复杂的 lambda 语法,因为我的库应该清楚供其他人使用。 这可以使用 C++ STL 和 Boost 吗?

【问题讨论】:

  • 为什么不只是超载呢?或者直接忽略其他两个参数
  • @Mgetz 因为我相信当我拥有超过 3 个 args 时,我可以再次面对这个问题,而且我在这里的重载越多,我的代码就会变得越丑陋和可读性越差。
  • 我认为您不希望将参数放在 struct 中以通过某些原因?

标签: c++11 lambda c++14 variadic-functions generic-lambda


【解决方案1】:

使用... 忽略以下参数怎么样?

loadData([](std::string body, ...){
    std::cout << body;
})


正如 StoryTeller 所指出的(谢谢!),对于非平凡的类型可能不支持使用省略号(有关更多详细信息,请参阅[expr.call]p9)。

为了避免这个问题,如果你可以使用C++14,你可以使用auto ...(更好的auto &amp;&amp; ...以避免不必要的复制;感谢Yakk)。

loadData([](std::string body, auto && ...){
    std::cout << body;
})

【讨论】:

  • 仅在有条件的情况下支持传递除最琐碎类型之外的任何内容。
  • @StoryTeller - 不确定理解;你的意思是... 保证只支持普通类型?
  • 我觉得改用未命名的模板参数包会更好。
  • @Revolver_Ocelot - 你是说auto ...吗?这是我的第一个想法,但不幸的是,它仅适用于 C++14,并且 OP 将此问题标记为 C++11。
  • 标签是“C++11 及更新版本”,所以我也添加了 C++14。您的解决方案很好,但我会在接受之前等待更好的方法。
【解决方案2】:

我受到其他答案之一的启发,该答案建议制作一个将正确数量的参数传递给函子的包装器。我发现这个解决方案非常好,并认为我会尝试制作一个通用的模板包装器,其中参数的数量不是硬编码的。这是我想出的:

#include <string>
#include <functional>
#include <iostream>

struct WrapperHelp
{
   template
      <  typename L
      ,  typename Tuple
      ,  std::size_t... Is
      ,  typename... Ts
      >
   static auto apply(L&& l, Tuple t, std::index_sequence<Is...>, Ts&&... ts)
      -> decltype(l(std::get<Is>(t)...))
   {
      return l(std::get<Is>(t)...);
   }

   template
      <  typename L
      ,  typename Tuple
      ,  std::size_t... Is
      ,  typename T1
      ,  typename... Ts
      >
   static auto apply(L&& l, Tuple t, std::index_sequence<Is...>, T1&& t1, Ts&&... ts)
      -> decltype(WrapperHelp::apply(std::forward<L>(l), std::forward_as_tuple(std::get<Is>(t)..., t1), std::make_index_sequence<sizeof...(Is) +1 >(), ts...))
   {
      return WrapperHelp::apply(std::forward<L>(l), std::forward_as_tuple(std::get<Is>(t)..., t1), std::make_index_sequence<sizeof...(Is) + 1>(), ts...);
   }
};

template<typename L>
struct OptionalWrapper {
   public:
      OptionalWrapper(L l) : lambda{std::move(l)} {}

      template<typename... Ts>
      void operator()(Ts&&... ts) const
      {
         WrapperHelp::apply(lambda, std::tuple<>(), std::index_sequence<>(), std::forward<Ts>(ts)...);
      }

   private:
      L lambda;
};

template<typename L>
auto makeOptionalWrapper(L l) { return OptionalWrapper<L>{std::move(l)}; }

template<class F>
void loadData(OptionalWrapper<F>&& callback)
{
   std::string body = "body";
   std::string subject = "subject";
   std::string header = "header";
   double lol  = 2.0;
   callback(body, subject, header, lol);
}

template<typename L>
void loadData(L callback)
{
    loadData(makeOptionalWrapper(std::move(callback)));
}

int main() {
   //apply(std::tuple<double>(2), std::tuple<double>(2));
   loadData([](auto&& body) { 
      std::cout << body << std::endl;
   });
   loadData([](auto&& body, auto&& subject) { 
      std::cout << body << " " << subject << std::endl;
   });
   loadData([](auto&& body, auto&& subject, auto&& header) { 
      std::cout << body << " " << subject << " " << header << std::endl;
   });
   loadData([](auto&& body, auto&& subject, auto&& header, auto&& lol) { 
      std::cout << body << " " << subject << " " << header << " " << lol << std::endl;
   });
   return 0;
}

这应该适用于任何函数,具有任意数量的“可选”参数,以及任何类型的参数。这不是最漂亮的代码,但我希望这个想法很清楚并且可以有一些用处:)

Live example

【讨论】:

  • 是的,这正是我所寻找的。非常感谢。
  • @DenisSheremet 没问题,很高兴为您提供帮助 :)
【解决方案3】:

您可以在 lambda 周围制作一个包装器。

template<typename L>
struct OptionalWrapper {
    OptionalWrapper(L l) : lambda{std::move(l)} {}

    void operator()(std::string body, std::string subject, std::string header) const {
        call(lambda, body, subject, header);
    }

private:
    template<typename T>
    auto call(T& l, std::string body, std::string subject, std::string header) const
        -> decltype(l(body, subject, header))
    {
        return l(body, subject, header);
    }

    template<typename T>
    auto call(T& l, std::string body, std::string subject, std::string) const
        -> decltype(l(body, subject))
    {
        return l(body, subject);
    }

    template<typename T>
    auto call(T& l, std::string body, std::string, std::string) const
        -> decltype(l(body))
    {
        return l(body);
    }

    L lambda;
};

template<typename L>
auto makeOptionalWrapper(L l) { return OptionalWrapper<L>{std::move(l)}; }

然后,像这样使用你的包装器:

void loadData(std::function<void (std::string, std::string, std::string)> callback)
{
    callback(body, subject, header);
}

template<typename L>
void loadData(L callback)
{
    loadData({makeOptionalWrapper(std::move(callback))});
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-22
    • 2020-09-28
    • 2012-01-06
    • 1970-01-01
    • 1970-01-01
    • 2015-10-30
    • 1970-01-01
    相关资源
    最近更新 更多