Search code examples
c++11lambdac++14variadic-functionsgeneric-lambda

How to pass first N args to a C++ function


I've got a function like this:

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

The problem is I'm not necessarily need to use subject and header in my callback function. Now I'm handling it this way:

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

I want to replace it with

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

and automatically pass to callback function as many arguments as it able to accept. I don't want to manually overload loadData function for all 3 possible argument counts. I also don't want to use any more complicated lambda syntax on the calling site because my library should be clear for others to use. Is this possible using C++ STL and Boost?


Solution

  • I got inspired by one of the other answers, which proposes to make a wrapper that passes the correct number of parameters to the functor. I find this solution very nice, and thought I would try make a general templated wrapper, where the number of arguments is not hardcoded. Here is what I came up with:

    #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;
    }
    

    This should work for any function, with any number of "optional" parameters, and with any types of parameters. It is not the prettiest code, but I hope the idea is clear and can be of some use :)

    Live example