23

I'm having lambdas that don't capture anything, like

[](){};

I have a template class, that contains such a lambda. Since the lambda does not contain non-static data members, nor virtual functions, it should be an empty class and DefaultConstructible. It's only a kind of policy class usable for template metaprogramming. I would like to know, why such a class is not default constructible by the C++ standard.

Sidenote: Understanding how Lambda closure type has deleted default constructor is asking a different question, though the title seems to be very similar. It is asking how a stateless lambda-object is created without usable default constructor. I'm asking why there is no usable default constructor.

Community
  • 1
  • 1
Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • 2
    [This question](http://stackoverflow.com/questions/32911729/understanding-how-lambda-closure-type-has-deleted-default-constructor) might be relevant. – Ami Tavory Aug 02 '16 at 14:06
  • How would you default-construct a lambda? `[]{}` already constructs one and gives you the instance. – rustyx Aug 02 '16 at 14:08
  • 1
    @rustyx Like so: `auto f = ()[]{}; using f_t = decltype(f); f_t{};` — this works, but for the fact that lambdas have deleted default constructors. – Konrad Rudolph Aug 02 '16 at 14:09
  • fyi http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1891 – Richard Critten Aug 02 '16 at 14:12
  • 1
    @RichardCritten Interesting in itself, but does that change anything wrt this question? The change from 'deleted' to 'no' default constructor was already noted in [the answer](http://stackoverflow.com/a/32913392/2757035) linked by Ami Tavory. – underscore_d Aug 02 '16 at 14:16
  • 1
    "*It's only a kind of policy class usable for template metaprogramming.*" Then make it an *actual class*. `struct Typename{};` is not that much longer than your lambda expression. And it has the added benefits of not creating an object of that type when you're only going to use the type for policies. Lambdas do not exist as a quick means for building structs. – Nicol Bolas Aug 02 '16 at 14:18
  • @KonradRudolph to be honest I'm glad that doesn't work. Simple `struct`s are way more expressive for metaprogramming. There is no need to abuse lambdas like that. – rustyx Aug 02 '16 at 14:22
  • Thinking about it, wouldn't allowing default construction for lambdas raise some thorny issues? For an empty capture group the lambda could just as easily be a function, and for a non-empty capture group you'd need to specify what the default values for each variable would be, and that would be more coherently expressed in a class or struct. What would you do with captures by reference? It seems like it might needlessly add considerations to lifetime management if you need to be sure nothing of a type is constructed outside the lifetime of a variable. – jaggedSpire Aug 02 '16 at 14:29
  • @jaggedSpire you could disable default constructor just for capturing lambdas, like it is done with function pointer conversion. – Revolver_Ocelot Aug 02 '16 at 14:31
  • @Revolver_Ocelot you could, but at that point you're gaining practically nothing beyond some syntactic sugar atop a class templated on function pointers like in Yakk's answer. – jaggedSpire Aug 02 '16 at 14:33
  • @NicolBolas Actually it should be a struct of the form `struct Policy { static ReturnType f( const Arg & ) { ... } }`. Instead of naming the function `f` I would use `operator()` when using a lambda. Actually lambdas exist exactly for this purpose: to write structs like that more concisely and in the place where its used. Without the lambda, the struct has to be defined somewhere out of line and more verbose. – Ralph Tandetzky Aug 09 '16 at 07:18
  • @RalphTandetzky: "*Actually lambdas exist exactly for this purpose*" No, lambdas exist to write *functors*. If you're trying to use the type of a lambda as some kind of policy, then you're doing it wrong. A lambda should mean simply that you can store it and call it; nothing more. – Nicol Bolas Aug 09 '16 at 13:54

3 Answers3

11

Lambdas are intended to be created then used. The standard thus says "no, they don't have a default constructor". The only way to make one is via a lambda expression, or copies of same.

They are not intended for their types to be something you keep around and use. Doing so risks ODR violations, and requiring compilers to avoid ODR violations would make symbol mangling overly complex.

However, in C++17 you can write a stateless wrapper around a function pointer:

template<auto fptr>
struct function_pointer_t {
  template<class...Args>
  // or decltype(auto):
  std::result_of_t< std::decay_t<decltype(fptr)>(Args...) >
  operator()(Args&&...args)const
    return fptr(std::forward<Args>(args)...);
  }
};

And as operator void(*)() on [](){} is constexpr in C++17, function_pointer_t<+[](){}> is a do-nothing function object that is DefaultConstructible.

This doesn't actually wrap the lambda, but rather the pointer-to-function that the lambda produces.

Marc Dirven
  • 309
  • 2
  • 18
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    Is the `+` in `function_pointer_t<+[](){}>` intentional, and if so, what does it mean? – Peter - Reinstate Monica Aug 02 '16 at 14:38
  • 4
    @PeterA.Schneider it [converts lambdas with empty capture groups to function pointers.](http://stackoverflow.com/questions/18889028/a-positive-lambda-what-sorcery-is-this) – jaggedSpire Aug 02 '16 at 14:43
  • Ok, I tried your solution. But with the C++17 I had available, it did not work. First it stumbled over `template `, which I could work around by using the type `void(*)()`. Then however, the compilers complained that `function_pointer_t<+[](){}>` uses a lambda expression inside a template argument (GCC) or that a lambda expression may not appear inside a constant expression (Clang). Is this due to incompleteness of GCC 6.1 and Clang 3.8 with respect to C++17, or does Yakk's answer not work according to the C++17 standard? – Ralph Tandetzky Aug 08 '16 at 06:56
  • @Yakk I don't understand how keeping the type around risks ODR violations. You can inherit from a lambda, you can use the type of a lambda as a template class type argument and it's actually really useful. – Ralph Tandetzky Aug 08 '16 at 07:01
  • 1
    @ralph `inline auto foo(){return []{};]` in a header file is an odr violation waiting to happen, at least in C++11. The type is highly unique. `std::map` also dangerous if it worked. All such techniques challenging to use standard compliantly. Feel free to ask a question on SO about it, or search google. – Yakk - Adam Nevraumont Aug 08 '16 at 11:18
  • @Yakk I see. You're correct. However, I don't care so much about ODR violations at the moment, since I'm not using this stuff in a header file, but only in a cpp file for internal implementation. Even if it was in a header file, weird things would only happen, if I retrieve the typeid or compare the types of two such objects by some other means. Apparently GCC and Clang can deal with it. I'm using GCC. It's good to know though and I'll keep it in the back of my brain. ;) – Ralph Tandetzky Aug 09 '16 at 07:40
  • 1
    @ralph You have described two possibke symptoms of undefined behaviour and then assumed they are the only things that can happen. UB can have unbounded downside. Do not assume current symptoms are all that can ever happen. As an example, signed overflow being used to eliminate entire branches of code. – Yakk - Adam Nevraumont Aug 09 '16 at 11:13
  • 6
    In C++20, lambdas without captures are default constructible. – Oktalist Oct 21 '20 at 00:24
3

With C++20, stateless lambdas are default constructible. So, now this kind of operations are valid:

auto func = []{};
decltype(func) another_func;
2

I'll assume that you're familiar with the difference between types, objects and expressions. In C++, lambda specifically refers to a lambda expression. This is a convenient way to denote a non-trivial object. However, it's convenience: you could create a similar object yourself by writing out the code.

Now per the C++ rules every expression has a type, but that type not what lambda expressions are intended for. This is why it's an unnamed and unique type - the C++ committee didn't think it worthwhile to define those properties. Similarly, if it was defined to have a default ctor, the Standard should define the behavior. With the current rule, there's no need to define the behavior of the default ctor.

As you note, for the special case of [](){} it's trivial to define a default ctor. But there's no point in that. You immediately get to the first hard question: for what lambda's should the default ctor be defined? What subset of lambda's is simple enough to have a decent definition, yet complex enough to be interesting? Without a consensus, you can't expect this to be standardized.

Note that compiler vendors, as an extension, could already offer this. Standardization often follows existing practice, see Boost. But if no compiler vendor individually thinks it worthwhile, why would they think so in unison?

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Actually, you can think of a lambda as being an object, that is created by a constructor that takes the capture list as constructor arguments. A lambda with empty capture list naturally should have a default constructor IMHO. – Ralph Tandetzky Aug 08 '16 at 07:25