2

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; });
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
S.H
  • 875
  • 2
  • 11
  • 27
  • you can use either template, or std::function, because all lambdas are required to be convertible into std::function – Creris May 06 '15 at 11:49
  • So the definition would be – int myfunc( const int a, std::function mylambda = [](int a, int b) { return a*b; }); – S.H May 06 '15 at 11:52
  • 1
    Related to [Passing lambda as function pointer](http://stackoverflow.com/q/28746744/1708801) – Shafik Yaghmour May 06 '15 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. – juanchopanza May 06 '15 at 11:55
  • For a default lambda see this question: http://stackoverflow.com/q/6025118/4834 – quamrana May 06 '15 at 13:13

2 Answers2

7

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).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 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 `int`s yields an `int`. – David Rodríguez - dribeas May 06 '15 at 12:23
  • @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. – Yakk - Adam Nevraumont May 06 '15 at 13:40
  • Could you explain "type erasure and allocation overhead" in more detail? – S.H May 07 '15 at 07:44
  • And could you explain "It will even overload correctly in C++14"? – S.H May 07 '15 at 07:46
  • 1
    @S.H. `std::function` 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". – Yakk - Adam Nevraumont May 07 '15 at 11:18
  • 1
    @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. – Yakk - Adam Nevraumont May 07 '15 at 11:20
  • @Yakk isn't it possible for the compiler to "inline" the lambda / std::function if it's known at compile time? Because that will be my predominant case, defining the lambda just above the call of "myfunc". – S.H May 07 '15 at 14:21
  • @S.H. Typical `std::function` implementations are *hard* to inline (it has to inline a heap allocation, a virtual function table lookup to a derived class, and getting data from the heap). I have not witnessed it happen. lambdas are *easy* to inline. I have yet to meet a compiler that cannot inline a lambda. – Yakk - Adam Nevraumont May 07 '15 at 14:25
  • @Yakk Does it make a difference if the lambda is given to a function that expects a std::function as input? In that case, does the lambda behavior survive (as I'd like it) or does the std::function behavior remain? – S.H May 07 '15 at 15:35
  • @S.H. a `std::function` type erases its argument: after that, optimizing over the call barrier becomes impractical. A `std::function` being passed to a `std::function` with a different (but compatible) signature has double type-erasure overhead (!), but the optimization block can only happen once. – Yakk - Adam Nevraumont May 07 '15 at 16:06
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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489