Lambda closure type constructors - c++

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?

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.

Related

std::optional: Not participating in overload resolution vs. being defined as deleted

I am trying to understand the mechanism behind type traits propagation as described for std::optional in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0602r4.html. There is a subtle difference in the treatment of copy operations, which shall be conditionally defined as deleted, versus move operations, which shall rather not participate in overload resolution.
What is the reason for that difference, and how would I test the latter? Example:
#include <type_traits>
#include <optional>
struct NonMoveable {
NonMoveable() = default;
NonMoveable(NonMoveable const&) = default;
NonMoveable(NonMoveable&&) = delete;
NonMoveable& operator=(NonMoveable const&) = default;
NonMoveable& operator=(NonMoveable&&) = delete;
};
// Inner traits as expected
static_assert(!std::is_move_constructible<NonMoveable>::value);
static_assert(!std::is_move_assignable<NonMoveable>::value);
// The wrapper is moveable, via copy operations participating in
// overload resolution. How to verify that the move operations don't?
static_assert(std::is_move_constructible<std::optional<NonMoveable>>::value);
static_assert(std::is_move_assignable<std::optional<NonMoveable>>::value);
int main(int argc, char* argv[])
{
NonMoveable a1;
NonMoveable a2{std::move(a1)}; // Bad, as expected
std::optional<NonMoveable> b1;
std::optional<NonMoveable> b2{std::move(b1)}; // Good, see above. But
// useless as a test for
// P0602R4.
return 0;
}
Bonus Question
Does GCC do the right thing? I have modified the example a bit to get a tiny step closer: https://godbolt.org/z/br1vx1. Here I made the copy operations inaccessible by declaring them private. GCC-10.2 with -std=c++20 now fails the static asserts and complains
error: use of deleted function 'std::optional<NonMoveable>::optional(std::optional<NonMoveable>&&)'
According to Why do C++11-deleted functions participate in overload resolution? the delete is applied after overload resolution, which could indicate that the move constructor participated, despite P0602R4 said it shall not.
On the other hand https://en.cppreference.com/w/cpp/language/overload_resolution states right in the beginning
... If these steps produce more than one candidate function, then overload resolution is performed ...
so overload resolution was skipped, because the move constructor was the only candidate?
std::optional is a red herring; the key is understanding the mechanisms that lead to why these requirements are placed on a library type
There is a subtle difference in the treatment of copy operations, which shall be conditionally defined as deleted, versus move operations, which shall rather not participate in overload resolution.
The under-the-hood requirements (and how to implement these) for std::optional are complex. However, the requirement that move operations shall not participate in overload resolution (for non-movable types) vs copy operations being deleted (for non-copyable types) likely relates to a separate topic;
NRVO(1) (a case of copy elision, if you may) and
more implicit moves, e.g. choosing move constructors over copy constructors when returning named objects with automatic storage duration from a function.
We can understand this topic by looking at simpler types than std::optional.
(1) Named Returned Value Optimization
TLDR
The move eagerness that is expanding in C++ (more eager moves in C++20) means there are special cases where a move constructor will be chosen over a copy constructor even if the move constructor has been deleted. The only way to avoid these for, say, non-movable types, is to make sure the type has no move constructor nor a move assignment operator, by knowledge of the rule of 5 and what governs whether these are defined implicitly.
The same preference does not exist for copying, and there would be no reasons to favour removing these over deleting them, if this was even possible (2). In other words, the same kinks that exist for favouring moves in overload resolution, that sometimes unexpectedly choose move over copy, is not present for the reverse; copy over move.
(2) There is no such thing as a class without the existence of a copy ctor and a copy assignment operator (although these may be defined as deleted).
Implicit move eagerness
Consider the following types:
struct A {
A() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
A(A const &) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
A &operator=(A const &) {
std::cout << __PRETTY_FUNCTION__ << "\n";
return *this;
}
};
struct B {
B() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
B(B const &) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
B &operator=(B const &) {
std::cout << __PRETTY_FUNCTION__ << "\n";
return *this;
}
B(B &&) = delete;
B &operator=(B &&) = delete;
};
Where, A:
has user-defined constructors, such that a move constructor and move assigment operator will not be implicitly defined,
and where B, moreover:
declares and deletes a move constructor and a move assignment operator; as these are declared and defined as deleted, they will participate in overload resolution.
Before we continue through different standard versions, we define the following functions that we shall return to:
A getA() {
A a{};
return a;
}
B getB() {
B b{};
return b;
}
C++14
Now, in C++14 an implementation was allowed to implement copy(/move) elision for certain scenarios; citing [class.copy]/31 from N4140 (C++14 + editorial fixes) [emphasis mine]:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. [...]
This elision of copy/move operations, called copy elision, is permitted in the following circumstances:
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
[...]
and, from [class.copy]/32 [emphasis mine]:
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
But [class.temporary]/1 still placed the same semantic restrictions on an elided copy of an object as if the copy had actually not been elided [emphasis mine]
[..] Even when the creation of the temporary object is unevaluated (Clause [expr]) or otherwise avoided ([class.copy]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed.
Such that, even for a situation where copy elision was eligible (and performed), the conversion sequences from, say, a named object viable for NRVO, would need to go through overload resolution to find (possibly elided) converting constructors, and would start with a pass through overload resolution as if the object were designated by an rvalue. This means, that in C++14, the following was well-formed
auto aa{getA()}; // OK, and copy most likely elided.
whereas the following was ill-formed:
auto bb{getB()}; // error: use of deleted function 'B::B(B&&)'
as overload resolution would find the declared but deleted move constructor of B during the step of considering b in return b; in getB() as an rvalue. For A, no move constructor exists, meaning overload resolution for a in return a; in getA() with a as an rvalue would fail, and whereafter overload resolution without this kink would succeed in finding the copy constructor of A (which would subsequently be elided).
C++17
Now, in C++17 copy elision was made stronger by the concept of delayed (end entirely elided) materialization of temporaries, particularly adding [class.temporary]/3 [emphasis mine]:
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the non-deleted trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).
This makes a large difference, as copy elision can now be performed for getB() without passing through the special rules of return value overload resolution (which previously picked the deleted move constructor), such that both of these are well-formed in C++17:
auto aa(getA()); // OK, copy elided.
auto bb(getB()); // OK, copy elided.
C++20
C++20 implements P1825R0 which allows even more implicit moves, expanding the cases where move construction or assignment may take place even when one would, at first glance, expect a copy construction/assignment (possible elided).
Summary
The quite complex rules with regard to move eagerness (over copying) can have some unexpected effects, and if a designer wants to make sure a type will not run into a corner case where a deleted move constructor or move assignment operator takes precedence in overload resolution over an non-deleted copy constructor or copy assignment operator, it is better to make sure that there are no move ctor/assignment operator available for overload resolution to find (for these cases), as compared to declaring them and defining them as explicitly-deleted. This argument does not apply for the move ctor/copy assignment operator however, as:
the standard contains no similar copy-eagerness (over move), and
there is no such thing as a class without a copy constructor or copy assignment operator, and removing these from overload resolution is basically(3) only possible in C++20 using requires-clauses.
As an example (and probably a GCC regression bug) of the difficulty of getting these rules right for a non-language lawyer, GCC trunk currently rejects the following program for C++20 (DEMO):
// B as above
B getB() {
B b{};
return b;
}
with the error message
error: use of deleted function 'B::B(B&&)'
In this case, one would expect a copy (possibly elided) to be chosen above in case B had deleted its move ctor. When in doubt, make sure the move ctor and assignment operator don't participate (i.e., exist) in overload resolution.
(3) One could declare a deleted assignment operator overloaded with both const- and ref-qualifiers, say const A& operator=(const A&) const && = delete;, which would very seldom be a viable candidate during overload solution (assignment to const rvalue), and which would guarantee the non-existence of the other non-const and &-qualified overloads that would otherwise likely to be valid overload candidates.

Why is it illegal to bind an r-value to a const l-value reference in special member functions?

For function parameters, it is possible to bind an r-value to an l-value const reference.
However, this does not seem to apply to special member function like the copy-constructor, and copy-assignment operator in C++11 and C++14. Is there a motivation for this?
When using C++17, it is possible to copy-construct, but not copy assign, from an r-value.
Is there a motivation why only the behavior for the copy-constructor was changed here?
All of this is demonstrated in the following example:
struct B {
B() = default;
B(B const&) = default;
B(B&&) = delete;
B& operator=(B const&) = default;
B& operator=(B&&) = delete;
};
void bar(B const &) {}
int main() {
bar(B{}); // does work
B(B{}); // only works in C++17
B b{};
b = B{}; // doesn't work
}
B(B{}); works since C++17 because of mandatory copy elision, the move-construction is omitted completely; the temporary object is initialized by the default constructor directly.
(emphasis mine)
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
...
In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the
variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.
Before C++17 this is an optimization and B(B{}); is ill-formed.
This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed
bar(B{}); works because there's only one overlaod of bar which taking lvalue-reference to const, to which rvalues could be bound; this doesn't change since C++11.
b = B{}; doesn't work because the overloaded move assignment operator is selected; even it's marked as delete explicitly it still participates in overload resolution[1]. For bar if you add an overloading taking rvalue-reference as
void bar(B&&)=delete;
It'll be selected and cause the program ill-formed too.
[1] Note that it's not true for deleted implicitly declared move constructors, which are ignored by overload resolution. (since C++14)
The deleted implicitly-declared move constructor is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue).

How to avoid triggering this kind of copy constructor in c++11?

I want to create an object in a function and use it outside.
I write the following code under the c++17 standard, it seems ok.
#include <iostream>
struct Vector2 {
Vector2() = default;
Vector2(int x, int y) : x(x), y(y) {}
Vector2(const Vector2 &) = delete;
Vector2 &operator=(const Vector2 &) = delete;
int x = 0;
int y = 0;
};
Vector2 newVec(int x, int y) {
return Vector2(x, y);
}
int main() {
auto v = newVec(1, 2);
std::cout << v.x * v.y << std::endl;
return 0;
}
But when I switched to c++11 standard, I couldn't compile it.
note: 'Vector2' has been explicitly marked deleted here
Vector2(const Vector2 &) = delete;
I think I constructed a temporary Vector2 object in the newVec function. When return, Vector2 call copy constructor with the temporary variable as the argument. But I have marked copy constructor 'delete', so it can't be compiled.
My question is what is the equivalent code under the C++11 standard?
What did c++17 do to avoid this copy constructor?
What did c++17 do to avoid this copy constructor?
This:
auto v = newVec(1, 2);
was one of the motivating cases for a language feature called "guaranteed copy elision." The example from that paper:
auto x = make(); // error, can't perform the move you didn't want,
// even though compiler would not actually call it
Before C++17, that expression always copy-initialization. We deduce the type with auto, that gives us Vector2, and then we're trying to construct a Vector2 from an rvalue of type Vector2. That's the usual process - enumerating constructors, etc. The best match is your copy constructor, which is deleted, hence the whole thing is ill-formed.
Sad face.
In C++17, this is totally different. Initializing from a prvalue of the same type doesn't try to find a constructor at all. There is no copy or move. It just gives you a Vector2 that is the value of newVec(1, 2), directly. That's the change here - it's not that in C++17 that copy-initialization works, it's more that it's not even the same kind of initialization anymore.
My question is what is the equivalent code under the C++11 standard?
There is no way at all to construct a non-movable object like this. From the same paper:
struct NonMoveable { /* ... */ };
NonMoveable make() { /* how to make this work without a copy? */ }
If you want a function like make() (newVec() in your example) to work, the type has to be, at least, movable. That means either:
adding a move constructor
undeleting your copy constructor
if none of the above is possible, put it on the heap - wrap it in something like unique_ptr<Vector2>, which is movable
Or you pass into make() your object and have the function populate it internally:
void make(NonMoveable&);
This ends up being less compose-able, but it works. As written, it requires your type to be default constructible but there are ways around that as well.
You are observing guaranteed copy elision in C++17.
On the other hand, C++11 requires that a copy or move constructor is present and accessible even if the compiler ultimately decides to elide its invocation.
You need to either remove the explicit deletion of the copy constructor so that the move constructor gets generated(but so will be the copy constructor) or declare a move constructor like so:
Vector2(Vector2 &&) = default;
According to the standard,
If the definition of a class X does not explicitly declare a move
constructor, one will be implicitly declared as defaulted if and only
if
— X does not have a user-declared copy constructor,
— X does not have a user-declared copy assignment operator,
— X does not have a user-declared move assignment operator, and
— X does not have a user-declared destructor.
User-declared means either either user-provided (defined by the user), explicitly defaulted (= default) or explicitly deleted (= delete)
On the other hand in C++17, call to a copy/move constructor is omitted due to the reason described here:
Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to.
...
(Since C++17) In the initialization of an object, when the source object is a nameless temporary and is of the same class type (ignoring cv-qualification) as the target object. When the nameless temporary is the operand of a return statement, this variant of copy elision is known as RVO, "return value optimization".
(until C++17)
Return value optimization is mandatory and no longer considered as copy elision; see above.
You can fix it with two small changes:
return {x, y}; from newVec(). This will construct the object just once and not require a copy constructor.
const auto& v instead of auto v. This will have the same lifetime (but you can't modify it), and doesn't require a copy constructor.

Understanding how Lambda closure type has deleted default constructor

From 5.1.2
[19] 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 implicitlydeclared
move constructor (12.8). [ Note: The copy/move constructor is implicitly defined in the same way
as any other implicitly declared copy/move constructor would be implicitly defined. —end note ]
I'm reading through C++ Primer 14.8.1 which explains lambda expressions being translated by the compiler in to an unnamed object of an unnamed class. How is it I can I define objects of lambda functions which do not contain a lambda capture, if the default constructor is deleted?
auto g = [](){};
Is this not conceptually the same as...
class lambdaClass{
public:
lambdaClass() = delete;
lambdaClass& operator=(const lambdaClass&) = delete;
void operator()(){ }
//copy/move constructor and destructor implicitly defined
};
auto g = lambdaClass(); //would be an error since default is deleted.
If there was a capture then a constructor other than the default constructor would be defined and it would be okay to initialise objects of such (as long as a parameter was passed). But if there is no capture and the default constructor is deleted, it doesn't seem consistent conceptually that a lambda class object can be created.
Edit: Hmm, maybe the conception that the lambda class creates constructors depending on its lambda captures is unfounded despite this being how it's described in C++ Primer (I can find no quote of it in the standard), because the following code does not work even though I would expect it to conceptually:
int sz = 2;
auto a = [sz](){ return sz;};
decltype(a) b(10); //compiler error
decltype(a) b = a; //all good though
The relationship between a closure to lambda is similar to object to class.
The C++11 standard says that the closure! type has no default constructor, and that is correct because it doesn't say it has no constructor.
The lambda is used to create a closure. But your quoted paragraph will change for C++14.
ClosureType() = delete; // (until C++14)
ClosureType(const ClosureType& ) = default; // (since C++14)
ClosureType(ClosureType&& ) = default; // (since C++14)
Closure types are not DefaultConstructible. Closure types have a deleted (until C++14) no (since C++14) default constructor. The copy constructor and the move constructor are implicitly-declared (until C++14) declared as defaulted (since C++14) and may be implicitly-defined according to the usual rules for copy constructors and move constructors.
http://en.cppreference.com/w/cpp/language/lambda
because the right side of assignment is a temp object(rvalue), so the 'g' is assigned by move assignment

Do these two C++ initializer syntaxes ever differ in semantics?

Assume that the following code is legal code that compiles properly, that T is a type name, and that x is the name of a variable.
Syntax one:
T a(x);
Syntax two:
T a = x;
Do the exact semantics of these two expressions ever differ? If so, under what circumstances?
If these two expressions ever do have different semantics I'm also really curious about which part of the standard talks about this.
Also, if there is a special case when T is the name of a scalar type (aka, int, long, double, etc...), what are the differences when T is a scalar type vs. a non-scalar type?
Yes. If the type of x is not T, then the second example expands to T a = T(x). This requires that T(T const&) is public. The first example doesn't invoke the copy constructor.
After the accessibility has been checked, the copy can be eliminated (as Tony pointed out). However, it cannot be eliminated before checking accessibility.
The difference here is between implicit and explicit construction, and there can be difference.
Imagine having a type Array with the constructor Array(size_t length), and that somewhere else, you have a function count_elements(const Array& array). The purpose of these are easily understandable, and the code seems readable enough, until you realise it will allow you to call count_elements(2000). This is not only ugly code, but will also allocate an array 2000 elements long in memory for no reason.
In addition, you may have other types that are implicitly castable to an integer, allowing you to run count_elements() on those too, giving you completely useless results at a high cost to efficiency.
What you want to do here, is declare the Array(size_t length) an explicit constructor. This will disable the implicit conversions, and Array a = 2000 will no longer be legal syntax.
This was only one example. Once you realise what the explicit keyword does, it is easy to dream up others.
From 8.5.14 (emphasis mine):
The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see class.temporary, class.copy.
So, whether they're equivalent is left to the implementation.
8.5.11 is also relevant, but only in confirming that there can be a difference:
-11- The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; see below. A parenthesized initializer can be a list of expressions only when the entity being initialized has a class type.
T a(x) is direct initialization and T a = x is copy initialization.
From the standard:
8.5.11 The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; see below. A parenthesized initializer can be a list of expressions only when the entity being initialized has a class type.
8.5.12 The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form
T x = a;
The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form
T x(a);
The difference is that copy initialization creates a temporary object which is then used to direct-initialize. The compiler is allowed to avoid creating the temporary object:
8.5.14 ... The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.
Copy initialization requires a non-explicit constructor and a copy constructor to be available.
In C++, when you write this:
class A {
public:
A() { ... }
};
The compiler actually generates this, depending on what your code uses:
class A {
public:
A() { ... }
~A() { ... }
A(const A& other) {...}
A& operator=(const A& other) { ... }
};
So now you can see the different semantics of the various constructors.
A a1; // default constructor
A a2(a1); // copy constructor
a2 = a1; // copy assignment operator
The copy constructors basically copy all the non-static data. They are only generated if the resulting code is legal and sane: if the compiler sees types inside the class that he doesn't know how to copy (per normal assignment rules), then the copy constructor won't get generated. This means that if the T type doesn't support constructors, or if one of the public fields of the class is const or a reference type, for instance, the generator won't create them - and the code won't build. Templates are expanded at build time, so if the resulting code isn't buildable, it'll fail. And sometimes it fails loudly and very cryptically.
If you define a constructor (or destructor) in a class, the generator won't generate a default one. This means you can override the default generated constructors. You can make them private (they're public by default), you can override them so they do nothing (useful for saving memory and avoiding side-effects), etc.