1

This is my code:

#include<iostream>

struct Item {
  int val;
};

struct XItem {
  int val;
};

void transform(const Item &i, XItem &target) {
  target.val = i.val;
}

template<typename T>
void show(const T &v) {
  std::cout << v.val << std::endl;
}

template<typename ParamsType, typename ResultType>
void handleRequest(Item &cur, ResultType (*impl)(const ParamsType &p)) {
  ParamsType p{};
  transform(cur, p);
  ResultType res = (*impl)(p);
  show(res);
}

struct ResItem {
  int val;
};

int main(int argc, char *argv[]) {
  Item i{42};
  handleRequest(i, [](const XItem &x) {
    return ResItem{x.val};
  });
  return 0;
}

Compiling it gives the following error:

test.cpp:33:3: error: no matching function for call to 'handleRequest'
  handleRequest(i, [](const XItem &x) {
  ^~~~~~~~~~~~~
test.cpp:21:6: note: candidate template ignored: could not match 'ResultType
      (*)(const ParamsType &)' against '(lambda at test.cpp:33:20)'
void handleRequest(Item &cur, ResultType (*impl)(const ParamsType &p)) {
     ^

I am unsure why this happens, however I do suspect that because the lambda, while being implicitly convertible to a function pointer, isn't one, the template parameters can't be deduced from it.

I tried using std::function<ResultType(const ParamsType &p)> instead, which also doesn't work. This question details the problem, so I tried to use its solution:

template<typename ParamsType, typename ResultType, typename Callback>
void handleRequest(Item &cur, Callback cb) {
  ParamsType p = transform(cur);
  ResultType res = std::invoke(cb, p);
  show(res);
}

However, now ParamsType and ResultType cannot be implicitly deduced from the callback, I would need to give them explicitly. But I really want to infer ResultType because in the actual code, it can be quite lengthy (it is inferred from the lambda return statement, which is more complex than in this minimal example). I need the two types because both transform and show are overloaded and need the types to bind.

Is it possible to have handleRequest infer those types in this scenario and if so, how?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
flyx
  • 35,506
  • 7
  • 89
  • 126
  • What about `auto p = transform(cur); auto res = cb(p);`? – super Aug 12 '20 at 11:12
  • If you need the types in the context of the function you can additionally use `decltype` – super Aug 12 '20 at 11:16
  • @super In actual code, I would have multiple transforms like `A transform(const Item&) {…}`, `B transform(const Item&) {…}` which would be illegal. – flyx Aug 12 '20 at 11:27
  • In that case extracting the types with a type-trait is the more idiomatic approch. Keeps the code more generic then requiring a function pointer to be passed. Especially since that limits you to lambdas without capture. – super Aug 12 '20 at 11:45
  • @super Mind that the body of `handleRequest` is vastly simplified. In actual code, `transform(cur, p);` is `cur.get_to(p);` from [this json library](https://github.com/nlohmann/json) which limits my possibilities. I gave the body to show how I use the types, not for getting advice of how not to use them there, I am sorry if that wasn't clear. – flyx Aug 12 '20 at 12:00
  • I don't see how that makes any difference. Extracting the types using a type trait is still a more idiomatic and general approach that solves the problem in your question. The body of `handleRequest` is more or less irrelevant in regards to that. – super Aug 12 '20 at 14:00
  • If it's not clear what I mean, you would place something like `using ParamType = extract_function_type::param_type; using ResultType = extract_function_type::result_type;` in the function body and you have the same thing but without limiting yourself to function pointers. – super Aug 12 '20 at 14:05
  • @super I can't see how I can specialize a type trait on the type of a lambda since that type is unspecified. You are welcome to post a working solution. – flyx Aug 12 '20 at 14:47
  • In the older standards you can take the type of the lambdas `operator()`, since c++17 using `decltype(std::function{cb})` gives you `std::function` which is very easy to feed to a type trait. There are plenty of solutions for that here on SO already. – super Aug 12 '20 at 14:56

1 Answers1

2

The problem is, implicit conversion (from lambda to function pointer) is not considered in template argument deduction, which fails deducing the template parameters.

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

You can specify the template arguments explicitly to bypass the deduction,

handleRequest<XItem, ResItem>(i, [](const XItem &x) {
  return ResItem{x.val};
});

Or convert the lambda to function pointer explicitly,

handleRequest(i, static_cast<RestItem(*)(const XItem&)>([](const XItem &x) {
  return ResItem{x.val};
}));

Or use the operator+ to convert the lambda to function pointer.

handleRequest(i, +[](const XItem &x) {
  return ResItem{x.val};
});
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • `operator+` is exactly what I was looking for since I don't need to give the types explicitly. – flyx Aug 12 '20 at 11:33
  • @flyx You might want to refer to [this post](https://stackoverflow.com/q/18889028/3309790) about how `operator+` magic does. And note that only non-capture lambda could covnert to function pointer. – songyuanyao Aug 12 '20 at 11:43
  • Thanks. Using only non-capture lambdas is perfectly viable for my use-case. – flyx Aug 12 '20 at 11:48