12

The cppreference shows that there are different rules for lambda closure type constructors.

Default Construction - Until C++14

ClosureType() = delete; (until C++14)

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

Default Construction - Since C++14

Closure types have no (since C++14) default constructor.

Default Construction - Since C++20

If no captures are specified, the closure type has a defaulted default constructor. Otherwise, it has no default constructor (this includes the case when there is a capture-default, even if it does not actually capture anything).

Copy Assignment Operator - Until C++20

The copy assignment operator is defined as deleted (and the move assignment operator is not declared). Closure types are not CopyAssignable.

Copy Assignment Operator - Since C++20

If no captures are specified, the closure type has a defaulted copy assignment operator and a defaulted move assignment operator. Otherwise, it has a deleted copy assignment operator (this includes the case when there is a capture-default, even if it does not actually capture anything).

What is the reason behind this change in the rules? Did standard committee identified some short comings in the standard for lambda closure type construction? If so, what are those short comings?

cpp_enthusiast
  • 1,239
  • 10
  • 28
  • 3
    yes. C++20 now allows lambdas in unevaluated contexts http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0315r1.pdf – bolov Apr 16 '19 at 12:31
  • 1
    @bolov interesting. I have to read this paper. However, can you please provide a brief explanation on how the unevaluated context effect the lambdas closure type default constructors? – cpp_enthusiast Apr 16 '19 at 12:36
  • 3
    @AImx1 Unevaluated context is not the reason. See this paper for the motivation: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0624r2.pdf – NathanOliver Apr 16 '19 at 12:41
  • @NathanOliver that is the correct motivation indeed. Thanks for the link – bolov Apr 16 '19 at 12:44

1 Answers1

11

There was a shortcoming. We couldn't use lambdas quite as "on the fly" as one might have wanted. C++20 (with the addition of allowing lambdas in unevaluated contexts) makes this code valid:

struct foo {
    int x, y;
};

std::map<foo, decltype([](foo const& a, foo const& b) { return a.x < a.y; })> m;

Note how we defined the compare function inline? No need to create a named functor (could be a good idea otherwise, but we aren't forced too). And there's no need to break the declaration in two:

// C++17
auto cmp = [](foo const& a, foo const& b) { return a.x < a.y; };
std::map<foo, decltype(cmp)> m(cmp); // And also need to pass and hold it!

Usages like this (and many more) were the motivating factor in making this change. In the example above, the anonymous functors type will bring all the benefits a named functor type can bring. Default initialization and EBO among them.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    FWIW, C++20 allows `std::map m;` now. – NathanOliver Apr 16 '19 at 12:40
  • So, no / deleting the default constructor during the lambda closure type construction until C++20 is a short coming basically. Is my understanding correct? – cpp_enthusiast Apr 16 '19 at 12:42
  • @NathanOliver - Indeed. But I think it's good to point out how two proposals synerergise. – StoryTeller - Unslander Monica Apr 16 '19 at 12:43
  • @AImx1 - There's nothing you couldn't accomplish without it. C++ is already Turing complete after all. But like the initial addition of lambdas, it opens up a new venue of ways to express programs. That's what should be taken from this, I think. It's the expressive power that they wished to improve. – StoryTeller - Unslander Monica Apr 16 '19 at 12:44
  • @StoryTeller indeed your are correct. The reason behind my question is to identify why the below program is not compiling. `int main() { auto compare = [](const char* lhs, const char* rhs) { return (strcmp(lhs, rhs) == 0); }; const std::set test{ "BS", "CA", "PR", "US" }; }` – cpp_enthusiast Apr 16 '19 at 12:49
  • 3
    @AImx1 - You need to pass `compare` as an argument to the constructor. Pass it after the initializer list: `test ({ "BS", "CA", "PR", "US" }, compare); ` - that's how it needs to be done prior to C++20. – StoryTeller - Unslander Monica Apr 16 '19 at 12:52
  • @StoryTeller Thanks for the suggestion. Is there any reason why the default constructor was deleted in pre-C++20 standard? – cpp_enthusiast Apr 16 '19 at 12:53
  • 4
    @AImx1 - Caution. lambda expressions was a new feature, and not all aspects of its behavior were clear to begin with. As experience with it grows, both by users and compiler writers, the committee comes back and reevaluates previously forbidden features. If you follow the standardization process, you'll start noticing it's a recurring theme. Better disable something when we aren't sure. Because it may always be enabled in the future :) – StoryTeller - Unslander Monica Apr 16 '19 at 12:56
  • 1
    @StoryTeller It make sense. Thanks for the explanation. – cpp_enthusiast Apr 16 '19 at 12:59