Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ how to infer the Callable's type (arguments list & return value) in a template

Basically what I want to do, is to make a function template, that takes any Callable (function type / lambda / Functor) and returns a lambda-taking-the-similar-args-list and returning the type of original's return type

#include <iostream>

int func(int a,float b) {
    return a+b;
}

struct callable {
    int operator() (int a, float b) {
        return a+b;
    }
};

template <typename RV, typename... Args>
auto getLambdaFromCallable(RV(&func)(Args...)) {
    auto l = [&](Args... args) -> RV {
        return func(args...);
    };

    return l;
}

int main() {
    auto f = getLambdaFromCallable(func);
    std::cout << f(1,2.f);
    std::cout << " " << typeid(f).name();

    auto f2 = getLambdaFromCallable(callable{}); // doesn't work
    callable{}(1,2); // works

    auto lambdaTest = [](int a, float b) -> int {
        return a+b;
    };
    auto f3 = getLambdaFromCallable(lambdaTest);
}
like image 418
barney Avatar asked Jan 03 '23 04:01

barney


1 Answers

You can change getLambdaFromCallable to:

template <typename F>
auto getLambdaFromFunction(const F& func) {
  auto l = [&](auto&&... args) 
    -> decltype(func(std::forward<decltype(args)>(args)...)) {
    return func(std::forward<decltype(args)>(args)...);
  };

  return l;
}

The reasoning behind this is that since you cannot get an exhaustive list of argument you can call a function object with (there might be multiple overloads in the first place), you might as well use a generic lambda which accepts everything and forward it to the callable.


To elaborate more about how this works:

  • The auto&&... part gets converted to a template argument list on the lambda's call operator.

  • F is deduced to whatever you called the getLambdaFromFunction with (without the const and reference but that can be changed if needed).

  • decltype(args) is just there to use std::forward which in turn is there to correctly forward both lvalue and rvalue references, see std::forward for more details.

The generated lambda object will look like this:

template <typename F>
class generatedLambda
{
public:
  template <typename... Args>
  auto operator()(Args&&... args) -> decltype(func(std::forward<decltype(args)>(args)...))
  {
    return func(std::forward<decltype(args)>(args)...);
  }

private:
  F     func;
};
like image 78
Drax Avatar answered Jan 13 '23 10:01

Drax