63

Example:

#include <functional>

int main() {
  auto test = []{};
  test = []{};
    
  return 0;
}

This emits the following error message in gcc 4.7.2:

test.cpp: In function ‘int main()’:
test.cpp:5:13: error: no match for ‘operator=’ in ‘test = <lambda closure object>main()::<lambda()>{}’
test.cpp:5:13: note: candidate is:
test.cpp:4:16: note: main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&) <deleted>
test.cpp:4:16: note:   no known conversion for argument 1 from ‘main()::<lambda()>’ to ‘const main()::<lambda()>&’

From the standard 5.1.2.3 (emphasis mine):

An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:

— the size and/or alignment of the closure type,

— whether the closure type is trivially copyable (Clause 9)

— whether the closure type is a standard-layout class (Clause 9), or

— whether the closure type is a POD class (Clause 9).

As far as I can tell, this is what I'm running up against. It's attempting to use a deleted assignment operator and failing. I am curious to know if there's an easy workaround, and more broadly what the motivating rationale for allowing copy constructibility to be omitted for lambdas generally.

Community
  • 1
  • 1
OmnipotentEntity
  • 16,531
  • 6
  • 62
  • 96
  • It's not attempting to use the copy constructor. It's attempting to use the assignment operator. – Benjamin Lindley Sep 12 '13 at 05:17
  • True! It is a bit late and I conflated the two. Generally though, the assignment operator is the same implementation. I can't speak to gcc or libstdc++ internals though. – OmnipotentEntity Sep 12 '13 at 05:18
  • Also, "whether a type is trivially copyable" is not the same as "whether a type is copyable". So the clause you highlighted does not imply that copy constructibility may be omitted, as you suggest. – Benjamin Lindley Sep 12 '13 at 05:21
  • Right, I've updated the question, I still think the part I highlighted is the problem area, because it's preventing me from using the assignment operator. Thanks for the eyeballs! – OmnipotentEntity Sep 12 '13 at 05:27
  • 11
    No, what's preventing you from using the assignment operator is 5.1.2.20 *The closure type associated with a lambda-expression has a deleted (8.4.3) default constructor and a deleted copy assignment operator. It has an implicitly-declared copy constructor (12.8) and may have an implicitly-declared move constructor (12.8).* (well, that, and the fact that the type of the lambda is different) – Benjamin Lindley Sep 12 '13 at 05:37
  • Note that "copyable" means it has copy constructor, but does not say anything about assignment operator. It would say "assignable" if it had assignment operator. – Jan Hudec Sep 12 '13 at 09:07
  • 20
    If you put a `+` before the first lambda, it magically starts to work. – Johannes Schaub - litb Sep 14 '13 at 11:12
  • @JohannesSchaub-litb Do you know why? – OmnipotentEntity Sep 16 '13 at 20:13
  • 2
    @OmnipotentEntity it's pure magic, I have no idea how it works! Does the `+` operator enable some special feature!? – Johannes Schaub - litb Sep 16 '13 at 21:03
  • @JohannesSchaub-litb, I would assume that it changes the detected type, ie the `operator+` returns a different type than the closure type. – OmnipotentEntity Sep 17 '13 at 12:00
  • 4
    @JohannesSchaub-litb I think I solved the puzzle. It was a bit long for a comment, so I made it a self-answer [here](http://stackoverflow.com/questions/18889028/a-positive-lambda-what-sorcery-is-this). – Daniel Frey Sep 19 '13 at 07:52
  • You may be able to redefine a lambda, if you do a placement `new` on the "old" lambda. – user1095108 Oct 13 '14 at 14:23

5 Answers5

71

You seem to think that those two lambdas have the same type, but that is not true. Each one creates its own type:

#include <functional>
#include <type_traits>
#include <iostream>

int main() {
  auto test = []{};
  auto test2 = []{};
  std::cout << std::is_same< decltype( test ), decltype( test2 ) >::value << std::endl;
  return 0;
}

will print 0. Of course the error message you are getting from the compiler could be a little bit clearer in this regards...

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • 25
    Reference: [expr.prim.lambda]/3 "The type of the *lambda-expression* [...] is a unique, unnamed non-union class type — called the *closure type*" – dyp Sep 12 '13 at 05:30
  • @DyP Was about to search for it in the standard but you were faster. Thanks :) – Daniel Frey Sep 12 '13 at 05:31
  • Though it is (IMO) not obvious from that quote that the type can be different for each lambda expression. But didn't find a better one. – dyp Sep 12 '13 at 05:32
  • 11
    I don't know, the usage of the word unique pretty much shouts it. ;) Thanks! – OmnipotentEntity Sep 12 '13 at 05:33
  • 1
    Pretend that each lambda expression is a macro that replaces the expression with an unnamed class definition with an overloaded `operator()`, and member variables that hold captured variables. Even if the structure of two classes are the same, they are still different types. – Andrew Tomazos Sep 12 '13 at 05:39
  • 1
    Even if they were the same type you still would not be able to copy them because the copy constructor is deleted (see section 5.1.2.20 of the C++11 standard). The compiler is not even getting to the point where it needs to check if the types match before it fails. This is the reason why you are getting the "no match for operator=" error. See the example I give in my answer. – Rastaban Jan 03 '14 at 03:13
  • @Rastaban You are right that even with the same type they could not be copied. But if you look at the error message, the failure occurs because they are different types, the parameter for the copy-ctor is `const main()::&` and the actual parameter is `main()::`. The error messages is misleading because both `main()::`'s are different types. If the compiler would try to apply the copy-ctor (after it was selected from the candidate list), the error message would look [like this](http://coliru.stacked-crooked.com/a/4aefa95b0140837d). – Daniel Frey Jan 03 '14 at 08:20
47

The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non- union class type

So it is like you are doing the following:

struct {} a;
struct {} b;
a = b; // error, type mismatch

Use std::function if you want to assign different lambdas with the same signature to the same variable.

std::function<void()> f = []{};
f = []{}; //ok
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • 6
    Or, since they're non-capturing, `void(*f)()` will do too. This can be more efficient. – MSalters Sep 12 '13 at 07:12
  • 8
    The type is unique because the type encodes which particular function to call; the function is simply `operator()` of the type. This allows inlining the function in generic algorithms which is not possible with either function pointers or `std::function`. – Jan Hudec Sep 12 '13 at 09:04
  • @JanHudec: Sort of, even if two lambda expressions are identical, they are still distinct types. – Andrew Tomazos Sep 12 '13 at 16:24
  • @user1131467: If you have two classes with the same content, they are still distinct types too. – Jan Hudec Sep 13 '13 at 06:47
8

Lambda can't be redefined because each lambda is of a different, anonymous, incompatible type. They can be copied only if you pass them to a templated function (like std::function ctor) that would be able to deduce that type.

J.N.
  • 8,203
  • 3
  • 29
  • 39
6

The reason you are not able to do this is because the copy assignment operator for the lambda-expression is declared deleted, See section 5.1.2/20 of the standard. For a more clear (for unusual definitions of clear) see this code sample

template<class T> void f(T x1)
{
  T x2 = x1; // copy constructor exists, this operation will succeed.
  x2 = x1; // assignment operator, deleted and will cause an error
}
int main()
{
  f([]{});
  return 0;
}

Other answers have pointed out that each lambda has a unique type, but this is not the reason why you are getting that error. This example shows that even if the two lambdas have the same type, it still is not able to copy it. However you are able to copy it to a new variable. This is the reason your error message is complaining about missing operator= and not about their types being different. Although each lambda having it's own type does not help you out much either.

Rastaban
  • 881
  • 6
  • 8
  • +1. You should add the quote from the Standard (which is 5.1.2/19, not 5.1.2/20 by the way), to the answer as well. – jogojapan Jan 03 '14 at 03:29
  • 1
    @jogojapan Sorry, but this answer is wrong. Look at the OPs error message: The problem is the different type, although the message says `main()::` twice. Newer versions of GCC [fixed the message](http://coliru.stacked-crooked.com/a/64ce469c4646f091). See also my comment to Rastaban's comment on my answer. – Daniel Frey Jan 03 '14 at 08:26
  • @DanielFrey Oh I see. Thanks for pointing this out. (But shouldn't 5.1.2/19 still be mentioned in a complete answer? It's true that a lambda expression has a unique type, but the wording in 5.1.2/3 suggests the type is unique for the lambda expression, not for the closure object. Hence if the same expression `[]{}` is used twice in the same scope, the type could still be identical. Moreover, even if the type was different, implicit conversion might be possible..?) – jogojapan Jan 03 '14 at 10:41
  • 1
    @jogojapan 5.1.2/3 refers to "the *lambda-expression*", meaning a single occurrence of *lambda-expression* in the source. I therefore read it as saying that the "unique" in this sentence means that each occurrence in the source creates a unique type, even if the same token sequence occurs twice it's still two occurrences and each one has its own type. Also, there is no implicit conversion for those types and no other conversions are defined that could match here. – Daniel Frey Jan 03 '14 at 10:49
  • @jogojapan ...continued: But even though this answer doesn't really explains the OPs immediate problem, I think it's worth to keep it here. It shows that besides the underlying problem I addressed in my answer, he was making another thinko by assuming that even though lambda's are copyable, they are still not assignable in any case. Therefore: +1 for Rastaban :) – Daniel Frey Jan 03 '14 at 10:51
  • Thanks @jogojapan for pointing out the correct paragraph from the C++11 standard. In my current job we use C++1y, so my reference is to the latest draft standard. – Rastaban Jan 17 '14 at 07:46
2

If we could assign one lambda to another lambda of a different type, how do we copy the function bodies/definitions from that lambda to the other one? If we would be so stubborn, then we could use some member std::function-like type to be the one who will be copied. But that would be against the ol' C++ rule of not paying blah blah...

Mark Garcia
  • 17,424
  • 4
  • 58
  • 94