7

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?

max66
  • 65,235
  • 10
  • 71
  • 111
Denis Sheremet
  • 2,453
  • 2
  • 18
  • 34

3 Answers3

6

What about ignoring the following arguments using ... ?

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


As pointed by StoryTeller (thanks!) the use of ellipsis can be unsupported for non trivial types (see [expr.call]p9 for more details).

To avoid this problem, if you can use C++14, you can use auto ... (better auto && ... to avoid unnecessary copies; thanks Yakk).

loadData([](std::string body, auto && ...){
    std::cout << body;
})
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
max66
  • 65,235
  • 10
  • 71
  • 111
  • 3
    Passing anything but the most trivial types is only conditionally supported. – StoryTeller - Unslander Monica Oct 16 '17 at 18:10
  • @StoryTeller - not sure to understand; do you mean that `...` is guaranteed to support only trivial types? – max66 Oct 16 '17 at 18:15
  • I think it would be better to use unnamed tempate parameter pack instead. – Revolver_Ocelot Oct 16 '17 at 18:24
  • @Revolver_Ocelot - do you mean `auto ...`? Was my first idea but, unfortunately, it's available only from C++14 and the OP tagged this question as C++11. – max66 Oct 16 '17 at 18:30
  • The tags is meant to be "C++11 and newer", so i've added C++14 as well. Your solution is good, but I'll wait a bit for a better approaches before accepting it. – Denis Sheremet Oct 16 '17 at 18:34
  • Can be unsupported means what exactly? Is it mostly safe to use until you run into problems? – Post Self Oct 16 '17 at 18:35
  • @kim366 - sorry but I'm not a standard expert and the page pointed by StoryTeller is (for me, at least) a little obscure; anyway I read words as "Passing a potentially-evaluated argument of class type having a non-trivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics" so I suppose the compiler, in these circumstances, can do what it wants. – max66 Oct 16 '17 at 18:39
  • @DenisSheremet - well... if C++14 is well for you, forget the C-style version. – max66 Oct 16 '17 at 18:41
1

You could make a wrapper around the 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)}; }

Then, use your wrapper like that:

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))});
}
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
1

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

Banan
  • 434
  • 6
  • 12