3

I want to define a function that takes (besides its usual input arguments) a lambda function. And I want to restrict that function as far as possible (its own input- and return types).

int myfunc( const int a, LAMBDA_TYPE (int, int) -> int mylamda ) { return mylambda( a, a ) * 2; } 

Such that I can call the function as follows:

int input = 5; myfunc( input, [](int a, int b) { return a*b; } ); 

What is the correct way to define myfunc?

And is there a way to define a default lambda? Like this:

int myfunc( const int a, LAMBDA_TYPE = [](int a, int b) { return a*b; }); 
6
  • you can use either template, or std::function<int(int, int)>, because all lambdas are required to be convertible into std::function Commented May 6, 2015 at 11:49
  • So the definition would be – int myfunc( const int a, std::function<int(int, int)> mylambda = [](int a, int b) { return a*b; }); Commented May 6, 2015 at 11:52
  • 1
    Related to Passing lambda as function pointer Commented May 6, 2015 at 11:53
  • @S.H You may be better off with the function template. std::function may incur some overhead that isn't always necessary. Commented May 6, 2015 at 11:55
  • For a default lambda see this question: stackoverflow.com/q/6025118/4834 Commented May 6, 2015 at 13:13

2 Answers 2

8

If you take a std::function<int(int,int)> it will have overhead, but it will do what you want. It will even overload correctly in C++14.

If you do not want type erasure and allocation overhead of std::function you can do this:

template< class F, class R=std::result_of_t<F&(int,int)>, class=std::enable_if_t<std::is_same<int,R>{}> > int myfunc( const int a, F&& f ) 

or check convertability to int instead of sameness. This is the sfinae solution. 1

This will overload properly. I used some C++14 features for brevity. Replace blah_t<?> with typename blah<?>::type in C++11.

Another option is to static_assert a similar clause. This generates the best error messages.

Finally you can just use it: the code will fail to compile if it cannot be used the way you use it.

In C++1z concepts there will be easier/less code salad ways to do the sfinae solution.


1 On some compilers std::result_of fails to play nice with sfinae. On those, replace it with decltype(std::declval<F&>()(1,1)). Unless your compiler does not support C++11 (like msvc 2013 and 2015) this will work. (luckily 2013/3015 has a nice result_of).

Sign up to request clarification or add additional context in comments.

10 Comments

Note that this does not require that the lambda arguments are int, only that int is implicitly convertible to whatever the lambda takes. It does require that the result of calling the lambda with two ints yields an int.
@DavidRodríguez-dribeas nod, detecting that in the general case is nearly impossible. I could maybe do it using a multiple-inheritance technique with an alternative SFINAE (anything but int) template operator() that returns a private flag type with a result_of call looking for that alternative, and special case coding for function pointers. Wouldn't work if the callable passed in was final... can't think of another case that would break. Is relatively silly. Another approach would be to test invoke with {int},{int} instead of int,int to catch narrowing conversions.
Could you explain "type erasure and allocation overhead" in more detail?
@S.H. std::function<Signature> has a constructor to take function objects. As specified in C++11, that constructor accepts anything, but only compiles if the object passed is call-compatible with the Signature (convertible from-and-to the args and return value as appropriate). More recently, a requirement that it only participate in overload resolution if it would compile due to signature compatibility (early check) was added. This allows two functions that are overloaded only based on std::function signature differences to overload "properly".
@S.H. std::function is not free. It is typically implemented by copying whatever it stores onto the heap, and accessing it (calls, copies, etc) via virtual methods. Both of these operations have costs associated with it. The virtual call (or whatever implementation alternative) and heap allocation are both extra costs that other solutions don't require. The technique it uses to accept nearly anything callable is called "type erasure" or "run time concepts", and this comment is way to small to explain that. In practice, the biggest cost to the virtual call is blocking some optimizations.
|
1

There are two alternatives, the type-erasing std::function<signature> forces a particular signature which goes along the lines of your question, but it imposes some additional cost (mainly it cannot be inlined in general). The alternative is to use a template an leave the last argument as a generic object. This approach can be better from a performance point of view, but you are leaving the syntax checking to the compiler (which I don't see as a problem). One of the comments mentions passing a lambda as a function pointer, but that will only work for lambdas that have no capture.

I would personally go for the second option:

template <typename Fn> int myFunc(const int a, Fn&& f) { return f(a, a) * 2; } 

Note that while this does not explicitly force a particular signature, the compiler will enforce that f is callable with two int and yields something that can be multiplied by 2 and converted to int.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.