12

I'm aware that there's no default constructor for lambda closure type. But does that mean it's impossible to instantiate it after it being passed as a template parameter?

Consider the following minimal example:

#include <iostream>

template <typename FuncType>
std::pair<int,int> DoSomething() {
    return FuncType()(std::make_pair(1,1));
}

int main() {
    auto myLambda = [](std::pair<int,int> x) {
        return std::make_pair(x.first*2,x.second*2);
    };
    std::pair<int,int> res = DoSomething<decltype(myLambda)>();
    return 0;
}

For performance reasons, I can't use std::function to avoid virtual pointer calls. Is there a way to do this? I need to instantiate that lambda once and use it many times inside that function.

How does the standard library make it work when decltype(myLambda) is passed to something like std::map comparators in the template parameter?

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 2
    A lambda is mostly just syntactic sugar for an anonymous class with an `operator()`. Declare your own, named class, with your own `operator()` that implements your lambda. – Sam Varshavchik Jun 01 '19 at 13:48
  • @SamVarshavchik I'm hoping I won't have to restrict the user from using lambdas. I would like it to be just like the standard library example I mentioned. – The Quantum Physicist Jun 01 '19 at 13:50
  • I don't know what "user" means. This [now looks to be like a definite XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Maybe you should try asking what your real question is. No, not the one that you wrote here, but the real question to which you believe the answer is to use lambdas this way, and that's what you're asking about. Maybe there's a completely different solution to your real question, but without knowing what it is, nobody will be able to help you. – Sam Varshavchik Jun 01 '19 at 13:55
  • I think that you are looking for [this talk by Louis Dionne](https://www.youtube.com/watch?v=yTb6xz_FSkY), the author of [the paper that later proposed adding default constructors to stateless clousure types](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0624r2.pdf). – Mike Jun 01 '19 at 13:58
  • @SamVarshavchik I don't believe this is an XY problem. Let me try to clarify, maybe that helps. A user will call some function in my library (`DoSomething` in this case). The function has to take another function as a parameter (whether template or otherwise), just like I showed up there. The user doesn't have to bother with my internal implementation of `DoSomething`, but I know that my implementation will call that (templatized) function quite often, because of the algorithm I'm using. So, my implementation shouldn't restrict the user to use a lambda or a named functor. – The Quantum Physicist Jun 01 '19 at 14:00
  • @Mike Thanks. I'll watch that now. – The Quantum Physicist Jun 01 '19 at 14:01
  • 1
    I feel like I don't understand something here. Why not pass the lambda as a function parameter and call that many times? – Nikos C. Jun 01 '19 at 14:04
  • @NikosC. What will the parameter type be? – The Quantum Physicist Jun 01 '19 at 14:05
  • The parameter type will be what it is now, only deduced from the argument. – StoryTeller - Unslander Monica Jun 01 '19 at 14:06
  • @TheQuantumPhysicist, the standard library [cannot make it work for std::map and the like](https://wandbox.org/permlink/hg35mxfOJH7I7pqN) before c++20 – Mike Jun 01 '19 at 14:07
  • @Mike Weird... I remember doing this very often. Last time I did it was in `std::unique_ptr` deleter. – The Quantum Physicist Jun 01 '19 at 14:12
  • @TheQuantumPhysicist: Is there some reason why it *must* be spelled `auto myLambda = [](std::pair x){...};` instead of `struct MyLambda { auto operator(std::pair x){...};};`? At the end of the day, it's not *that* much longer... – Nicol Bolas Jun 01 '19 at 15:57
  • @NicolBolas Just for user convenience. – The Quantum Physicist Jun 01 '19 at 15:59
  • @TheQuantumPhysicist: But the `struct` version is just as convenient. Not only does it work pre-C++20, it also doesn't require `decltype` gymnastics. I can understand wanting to use a lambda if capturing is involved (since capturing through constructors is a huge pain), but once you take captures off the table, what's the point? – Nicol Bolas Jun 01 '19 at 16:13
  • @NicolBolas I'm not the one who will call that function. A user of that library would. I don't want to restrict users' options in the way they do the calls. If it were up to me in my internal program, I'd agree with you 100%. The solutions down there with passing lambdas' objects are reasonable and are a good compromise. For some reason it didn't occur to me to do that from the beginning. – The Quantum Physicist Jun 01 '19 at 16:20

3 Answers3

7

Although this feature is coming in C++20 (see songyuanyao's answer), you don't actually need that in this case. You can just pass the lambda as a function parameter of type FuncType and call that multiple times:

template <typename FuncType>
std::pair<int,int> DoSomething(FuncType f)
{
    return f(std::make_pair(1,1));
}

int main()
{
    auto myLambda = [](std::pair<int,int> x) {
        return std::make_pair(x.first*2,x.second*2);
    };
    std::pair<int,int> res = DoSomething(myLambda);
}
Nikos C.
  • 50,738
  • 9
  • 71
  • 96
5

I'm aware that there's no default constructor for lambda closure type.

Yes, this is true until C++20. (Note that since C++20 if no captures are specified, the closure type has a defaulted default constructor.)

Closure types are not DefaultConstructible. Closure types have a deleted (until C++14) no (since C++14) default constructor. (until C++20)

And

How does the standard library make it work when decltype(myLambda) is passed to something like std::map comparators in the template parameter?

There's nothing special for the standard library. If you specify a non-DefaultConstructible lambda as the comparator type for std::map, you have to pass an object to the constructor, std::map will initialized its comparator via copy; lambda has copy and move constructor.

You can change your code to the same way as std::map's constructor:

template <typename FuncType>
std::pair<int,int> DoSomething(const FuncType& f = FuncType()) {
    // auto z(f);   // you can take a copy if necessary
    return f(std::make_pair(1,1));
}

then

auto myLambda = [](std::pair<int,int> x) {
    return std::make_pair(x.first*2,x.second*2);
};
std::pair<int,int> res = DoSomething<decltype(myLambda)>(myLambda);

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
2

As others have pointed out, empty lambdas will become default-constructible in C++20.

You cannot wait for / switch to it?
No problem, it's easily emulated, as long as your lambda isn't generic.

Simply convert the lambda to a function-pointer, and pack that into a std::integral_constant:

template <class T>
constexpr auto make_constant(T t) noexcept
-> std::integral_constant<decltype(+t), +t>
{ return {}; }

Of course, in most cases where you can choose a function-objects type, you can also pass the initial value, which is a bit easier.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118