flyx
flyx

Reputation: 39768

How can I make C++ infer template type parameters from a lambda?

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?

Upvotes: 1

Views: 425

Answers (1)

songyuanyao
songyuanyao

Reputation: 173014

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

Upvotes: 2

Related Questions