A C++ wiki book refers to
... In C++0x, such an assignment operator is known as a unifying
assignment operator because it eliminates the need to write two
different assignment operators ...
for an assignment operator that takes it's class type by value:
String & operator = (String s) // the pass-by-value parameter serves as a temporary
{
s.swap (*this); // Non-throwing swap
return *this;
}
I tried googling the term, but it doesn't appear to be in widespread use.
Where does it come from?
It appears to be in reference to the unification that takes place in formal type systems. The thought is if the r- and l-values can be brought to the same type (unified) by only certain, legal substitutions, then the assignment is well-formed.
Wikipedia claims the idea was first given meaningful attention (and possibly its name) by John Alan Robinson.
I'm not sure who phrased it but the wiki book is wrong. The word "unifying" appears exactly zero times in the c++0x "standard" (you should really be using the phrase "C++11" nowadays, it was approved in August 2011).
The correct term is copy elision. From C++0x (n3242, the last I can get without shelling out money), section 12.8 Copying and moving class objects, /34:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects.
In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.
This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies) ...
Related
I want to accept classes which are "trivially copyable", in the sense that if I mem-copy the byte representation of one variable of the type into another, it will be usable, and nothing will have been broken in any way.
Looking at std::is_trivially_copyable, I see the requirements are:
Trivially copyable classes, i.e. classes satisfying following requirements:
At least one copy constructor, move constructor, copy assignment operator, or move assignment operator is eligible
Every eligible copy constructor (if any) is trivial
Every eligible move constructor (if any) is trivial
Every eligible copy assignment operator (if any) is trivial
Every eligible move assignment operator (if any) is trivial
Has a trivial non-deleted destructor
First and last requirement - check. But the other requirements are super-strong and not what I want! I'd be willing to "compromise" on having a trivial copy ctor as a requirement, and that's already quite different than what I really want.
So, what type trait can I use from the standard library, or write myself, to express the constraint I'm interested in?
I'm writing C++11 right now, so if your answer requires a later standard - write it, but mention that fact explicitly.
At least one copy constructor, move constructor, copy assignment operator, or move assignment operator is eligible
Has a trivial non-deleted destructor
First and last requirement - check. But the other requirements are super-strong
So, what type trait can I use from the standard library, or write myself, to express the constraint I'm interested in?
You could use (is_copy_constructible || is_move_constructible || is_assignable) && std::is_trivially_destructible.
I'd be willing to "compromise" on having a trivial ... copy ctor as a requirement
You could use std::is_trivially_copy_constructible.
if I mem-copy the byte representation of one variable of the type into another, it will be usable, and nothing will have been broken in any way.
However, those requirements won't be sufficient for this. You need the "super-strong" requirements for this. This is what std::is_trivially_copyable is for.
Why does this program call the copy constructor instead of the move constructor?
class Qwe {
public:
int x=0;
Qwe(int x) : x(x){}
Qwe(const Qwe& q) {
cout<<"copy ctor\n";
}
Qwe(Qwe&& q) {
cout<<"move ctor\n";
}
};
Qwe foo(int x) {
Qwe q=42;
Qwe e=32;
cout<<"return!!!\n";
return q.x > x ? q : e;
}
int main(void)
{
Qwe r = foo(50);
}
The result is:
return!!!
copy ctor
return q.x > x ? q : e; is used to disable nrvo. When I wrap it in std::move, it is indeed moved. But in "A Tour of C++" the author said that the move c'tor must be called when it available.
What have I done wrong?
You did not write your function in a way that allows copy/move elision to occur. The requirements for a copy to be replaced by a move are as follows:
[class.copy.elision]/3:
In the following copy-initialization contexts, a move operation might
be used instead of a copy operation:
If 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. If the first
overload resolution fails or was not performed, or if the type of the
first parameter of the selected constructor is not an rvalue reference
to the object's type (possibly cv-qualified), overload resolution is
performed again, considering the object as an lvalue.
The above is from C++17, but the C++11 wording is pretty much the same. The conditional operator is not an id-expression that names an object in the scope of the function.
An id-expression would be something like q or e in your particular case. You need to name an object in that scope. A conditional expression doesn't qualify as naming an object, so it must preform a copy.
If you want to exercise your English comprehension abilities on a difficult wall of text, then this is how it's written in C++11. Takes some effort to see IMO, but it's the same as the clarified version above:
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the copy/move
constructor and/or destructor for the object have side effects. [...]
This elision of copy/move operations, called copy elision, is
permitted in the following circumstances (which may be combined to
eliminate multiple copies):
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
When the criteria for elision of a copy operation are met or would be
met save for the fact that the source object is a function parameter,
and the object to be copied is designated by an lvalue, overload
resolution to select the constructor for the copy is first performed
as if the object were designated by an rvalue. If overload resolution
fails, or if the type of the first parameter of the selected
constructor is not an rvalue reference to the object's type (possibly
cv-qualified), overload resolution is performed again, considering the
object as an lvalue.
StoryTeller didn't answer the question: Why is the move c'tor not called? (And not: Why is there no copy elision?)
Here's my go: The move c'tor will be called if and only if:
Copy elision (RVO) is not performed. Your use of the ternary operator is indeed a way to prevent copy elision. Let me point out though that return (0, q); is a simpler way to do this if you just want to return q while suppressing copy elision. This uses the (in-)famous comma operator. Possibly return ((q)); might work, too, but I am not enough of a language lawyer to tell for sure.
The argument to return is an rvalue. This could be a temporary (more precisely, a prvalue), but these are also eligible for copy elision. Therefore, the argument to return must be an xvalue, such as std::move(q) if you want to ensure the move c'tor is called.
See also: C++ value categories
Some more technicalities of your example:
q and e are objects of type Qwe.
q.x > x ? q : e is an lvalue expression of type Qwe. This is because the expressions q and e are lvalues of type Qwe. The ternary operator just selects either of them.
std::move(q.x > x ? q : e) is an xvalue expression of type Qwe. The std::move simply turns (casts) the lvalue into an xvalue. As an aside, q.x > x ? std::move(q) : std::move(e) would also work.
The copy c'tor gets called in return q.x > x ? q : e; because it can be called with an lvalue of type Qwe (constness is optional), while, on the other hand, the move c'tor cannot be called with an lvalue and is therefore eliminated from the candidate set.
UPDATE: Addressing the comments by going into more depth… this is a really confusing aspect of C++!
Conceptually, in C++98, returning an object by value meant returning a copy of the object, so the copy c'tor would be called. However, the standard's authors considered that a compiler should be free to perform an optimization such that this potentially expensive copy (e.g. of a container) could be elided under suitable circumstances.
This copy elision means that, instead of creating the object in one place and then copying it to a memory address controlled by the caller, the callee creates the object directly in the memory controlled by the caller. Therefore, only the "normal" constructor, e.g. a default c'tor, is called.
Therefore, they added a passage such that the compiler is required to check that the copy c'tor — whether generated or user-defined – exists and is accessible (there was no notion yet of deleted functions for that matter), and must ensure that the object is initialized as-if it had been first created in a different place and then copied (cf. as-if rule), but the compiler was not required to ensure that any side effects of the copy c'tor would be observable, such as the stream output in your example.
The reason why the c'tor was still required to be there was that they wanted to avoid a scenario where a compiler was able to accept code that another would have to reject, simply because the former implemented an optional optimization that the latter did not.
In C++11, move semantics were added, and the committee very much wanted to use this in such a manner that a lot of existing return-by-value functions e.g. involving strings or containers would become more efficient. This was done in such a way that conditions were given under which the compiler was actually required to perform a move instead of a copy. However, the idea of copy elision remained important, so basically there were now four different categories:
The compiler is required to check for a usable (see above) move c'tor, but is allowed to elide it.
The compiler is required to check for a usable move c'tor, and has to call it.
The compiler is required to check for a usable copy c'tor, but is allowed to elide it.
The compiler is required to check for a usable copy c'tor, and has to call it.
… which in turn lead to four possible outcomes:
Compiler checks for move c'tor, but then elides it. (relates to 1. above)
Compiler checks for move c'tor and actually emits a call to it. (relates to 1. or 2. above)
Compiler checks for copy c'tor, but then elides it. (relates to 3. above)
Compiler checks for copy c'tor and actually emits a call to it. (relates to 3. or 4. above)
And the long optimization story doesn't end here, because, in C++17, the compiler is required to elide certain c'tor calls. In these cases, the compiler is not even allowed to demand that a copy or move c'tor is available.
Note that a compiler has always been free to elide even such c'tor calls that do not meet the standard requirements, under the protection of the as-if rule, for instance by function inlining and the following optimization steps. Anyway, a function call, conceptually, does not have to be backed by the actual machine instruction for the execution of a subroutine. The compiler is just not allowed to remove observable, otherwise defined behavior.
By now you should have noticed that, at least prior to C++17, it is very well possible for the same well-formed program to behave differently, depending on the compiler used and even optimization settings, if the copy rsp. move constructor has observable side effects. Also, a compiler that implements copy/move elision may do so for a subset of the conditions under which the standard allows it to happen. This makes your question almost impossible to answer in detail. Why is the copy/move c'tor called here, but not there? Well, it may be because of the requirements of the C++ standard, but it also may be the preference of your compiler. Maybe the compiler authors had time and leisure implementing the one optimization but not the other. Maybe they found it too difficult in the latter case. Maybe they just had more important stuff to do. Who knows?
What matters 99% of the time for me as a developer is to write my code in such a way that the compiler can apply the best optimizations. Sticking to common cases and standard practice is one thing. Knowing NRVO and RVO of temporaries is another thing, and writing the code such that the standard allows (or, in C++17, requires) copy/move elision, and ensuring that a move c'tor is available where it is beneficial (in case elision does not occur). Don't rely on side effects such as writing a log message or incrementing a global counter. These are not what a copy or move c'tor should commonly do anyway, except possibly for debugging or scholarly interest.
This question already has answers here:
How does guaranteed copy elision work?
(2 answers)
What are copy elision and return value optimization?
(5 answers)
Closed 5 years ago.
I learned, long ago, that copy initialization creates a temporary which is then used to initialize the destination, though the latter copy constructor is optimized out; but the compiler still pretends to use it, checking for existence and allowed access.
I noticed Herb Sutter mention, in updated GOTW posts, that this is no longer true when auto is used.
Specifically, Herb (writing in 2013) still states the familiar rules in general:
If x is of some other type, conceptually the compiler first implicitly converts x to a temporary widget object… Note that I said “conceptually” a few times above. That’s because practically compilers are allowed to, and routinely do, optimize away the temporary — from GOTW #1
He later notes that when auto is used (in auto w = x;) that only a single copy constructor is called because there is no way that x needs to be converted first.
In the case of a function returning an iterator (so it’s an rvalue):
After all, as we saw in GotW #1, usually that extra = means the two-step “convert to a temporary then copy/move” of copy-initialization—but recall that doesn’t apply when using auto like this. … presto! there cannot be any need for a conversion and we directly construct i. —GOTW #2 (emphasis mine)
If the type returned from the function was exactly the same type as the variable being initialized with copy initialization, the rule would be that the temporary gets optimized out but it still checks access on the copy constructor. Herb is saying that this is not the case with auto, but direct initialization is used.
There are other examples where he seems to be saying (though not with rigorous precision) that when using auto you no longer have the compiler pretend to use the copy constructor.
What’s going on? Has the rule changed? Is this some additional feature of auto that all the presentations have missed mentioning?
I examined the C++17 standard (n4659) and could not find any special mention of auto in the section on initialization, nor anything about initialization in the section on auto. So, I went back to basics and read the rules for initialization in detail. And boy, has it changed! In C++17, the meaning of copy-initialization is not what you learned earlier. The many answers here on SO, and tutorials that explain how the copy constructor is needed but then optimized out are all obsolete, and now wrong.
C++17 has changed the way temporaries are handled, in order to support more copy elision and to enable a way to explain mandatory copy elision. In a nutshell, prvalues are not temporaries that may be optimized away but are still logically copied (or moved) to their final home. Instead, prvalues are sort of in limbo, with no address and no real existence. If a temporary is actually needed, one is “materialized”. But the idea here is that the prvalue can be collapsed out and the code that creates a value (such as the return statement) can be matched up with the final target, avoiding the creation of a temporary.
You can see that this directly affects the meaning of initialization, and by itself cuts out the old rule about a temporary in copy initialization.
So here’s the deal:
the new initialization rules
direct-initialization vs copy-initialization
This description covers the case where an object of class type is being defined. That is, not a reference, not a primitive type, etc. To keep the description simple, I’m not mentioning move constructors every place that copy constructor is mentioned.
C c1 ( exp() ); //exp returns a value, not a reference.
C c2 (v);
C c3 = exp();
C c4 = v;
We have direct initialization (c1 and c2) and copy initialization (c3 and c4). The list forms will be covered separately.
The following cases are covered in order of priority. Each rule is described assuming that earlier rules did not match already.
prvalue
If the source is a prvalue (pure rvalue) and already the right type, we get the match-up between the place that created the value and the final destination. That is, in c1 and c3, the value created by the return statement in exp will be created directly in the variable. In underlying machine language, the caller determines where the return value will go and passes this to exp as another parameter or a dedicated register or whatnot. Here, the address where c1 (or c3) will go is passed for this purpose. It always did that (in optimized code); but logically the caller set up a temporary and then copied the temporary to c3, but was allowed to optimize out the copy. Now, there is no temporary. The direct pipeline from value creation inside the function to the declared variable in the caller is part of the specification.
Note that this new reality affects prvalues in general. A prvalue (pure rvalue) is what you have when a function returns an object by value. The passage that explains this case (§11.6 ¶17.6.1) doesn’t even mention copy vs direct forms!
This means that the copy constructor is not involved. Assuming the function had some way to create the object (a different constructor or private access), you can create a variable when you don’t have an accessible copy constructor at all. This can be handy for factories, where you want to control how objects are created and the type is not copyable and not movable.
direct, and sometimes copy
In direct-initialization (c2), the parameters are used to call a constructor. Being direct, explicit constructors are considered. This should be familiar enough.
In copy-initialization, if the value is the same class as the destination or even a derived class, the value is used as a parameter to a constructor. That is c4 where v is also of class C, or class D derived from C so it is-a C. We suppose that would be the copy constructor that’s chosen, but you can have special constructors for derived types. Being copy-initialization, explicit constructors are ignored. The copy constructor can’t be explicit anyway. But you could have C::C(const D&) that’s explicit!
copy
In remaining cases of copy initialization, some conversion is found. For c4 suppose v is of type E. The conversion functions are conversion operators defined in E (e.g. E::operator C()) and non-explicit constructors in C (e.g. C::C(const E&)). The result of the conversion is then used with direct-initialization. That sounds familiar… but here is the tricky part: the conversion function might produce a prvalue! A normal conversion operator will return by value, so the return value from that operator is constructed directly in c4. It’s possible to write a conversion operator that returns a reference, and is thus an lvalue. But converting constructors are always prvalues. So, it appears that if a constructor is used then copy-initialization is just as direct as direct-initialization. But now it’s a phantom prvalue, not a temporary, that goes away.
list initialization
C c5 { v1,foo(),7 };
C c6 = { v1,foo(),7 };
C c7 = {exp()};
First of all, if C has a special constructor for initialization lists, then that is used. None of the regular stuff applies.
Otherwise, it’s pretty much the same as before. There are a few differences (§11.6.4 ¶3.6):
Because of the new syntax, it’s possible to specify multiple arguments for a constructor in the copy-initialization form. For example, c5 and c6 both specify the same constructor arguments. In copy-initialization the explicit constructors are disallowed. Note that I wrote disallowed not ignored: In regular copy-initialization, explicit constructors are ignored and overload resolution uses only the non-explicit forms. But in copy-list-initialization, all constructors are used for overload resolution, but then if an explicit constructor is chosen you get an error (§16.3.1.7).
Another difference is that when using the list syntax, narrowing conversions (§11.6.4 ¶7) are flagged as errors if they would be used in implicit conversions.
Now for a big surprise: the temporary is back! in case c7, even though exp() is a prvalue, the rule is to match that to constructor arguments. So, the expression produced a value of type C, but then the value is used to pick a constructor (which will be the copy constructor). Since the copy constructor cannot be explicit, there is no difference between the direct and copy syntax.
What does a normal programmer need to know?
This is all rather complex, but a normal day-to-day coder only needs to understand a few simple rules.
prvalues are piped directly to the new home. So the details happened in the function return, and there really isn’t anything happening here. So, there is no meaningful distinction between copy and direct syntax.
copy-initialization vs direct-initialization affects explicit constructors.
direct-initialization specifically asks for a constructor; copy-initialization can use any way of converting.
for lists:
list-initialization can use a special list constructor (e.g. std::vector).
list-initialization gives constructor arguments, but doesn’t allow narrowing conversions.
So, there’s the new part about lists. But really, it’s something you need to unlearn: forget about the convert-then-(pretend to)copy business.
Alex Stepanov defined Regular Types as types satisfying certain properties around copying and equality. Now that C++11 has added move semantics to the realm of generic programming, Stepanov's definition is no longer complete. I'm looking for a good reference on regular types that includes their interaction with move semantics.
Summary:
For C++11 I would include:
move-ctor (noexcept)
move-assign (noexcept)
total ordering (operator<() for natural total order and std::less<> if a natural
total order does not exist).
hash<>
And would remove:
swap() (non-throwing) - replaced by move operations.
Commentary
Alex revisits the concept of a regular type in Elements of Programming. In fact, much of the book is devoted to regular types.
There is a set of procedures whose inclusion in the computational
basis of a type lets us place objects in data structures and use
algorithms to copy objects from one data structure to another. We call
types having such a basis regular, since their use guarantees
regularity of behavior and, therefore, interoperability. -- Section 1.5 of EoP
In EoP, Alex introduces the notion of an underlying_type which gives us a non-throwing swap algorithm that can be used to move. An underlying_type template isn't implementable in C++ in any particularly useful manner, but you can use non-throwing (noexcept) move-ctor and move-assign as reasonable approximations (an underlying type allows moving to/from a temporary without an additional destruction for the temporary). In C++03, providing a non-throwing swap() was the recommended way to approximate a move operation, if you provide move-ctor and move-assign then the default std::swap() will suffice (though you could still implement a more efficient one).
[ I'm on record as recommending that you use a single assignment operator, passing by value, to cover both move-assign and copy-assign. Unfortunately the current language rules for when a type gets a default move-ctor causes this to break with composite types. Until that is fixed in the language you will need to write two assignment operators. However, you can still use pass by value for other sink arguments to avoid combinatorics in handling move/copy for all arguments. ]
Alex also adds the requirement of total ordering (though there may not be a natural total order and the ordering may be purely representational). operator<() should be reserved for the natural total ordering. My suggestion is to specialize std::less<>() if a natural total ordering is not available, there is some precedent for that in the standard).
In EoP, Alex relaxes the requirements on equality to allow for representational-equality as being sufficient. A useful refinement.
A regular type should also be equationally complete (that is, operator==() should be implementable as a non-friend, non-member, function). A type that is equationally complete is also serializable (though without a canonical serialization format, implementing the stream operators are of little use except for debugging). A type that is equationally complete can also be hashed. In C++11 (or with TR1) you should provide a specialization of std::hash.
Another property of regular types is area() for which there is not yet any standard syntax - and likely little reason to actually implement except for testing. It is a useful concept for specifying complexity - and I frequently implement it (or an approximation) for testing complexity. For example, we define the complexity of copy as bounded by the time to copy the area of the object.
The concept of a regular type is not language-specific. One of the first things I do when presented with a new language is work out how regular types manifest in that language.
Constraints of generic programming are best stated in terms of expressions. A more modern rendition of the same constraint on copiability would be that both statements should be valid:
T b = a;
and
T b = ra;
where a is an lvalue with type T or const T and ra is an rvalue with type T or const T. (With similar post-conditions.)
This formulation is in the spirit of the paper, I believe. Do note that C++03 already makes use of notions like lvalues and rvalues, such that the constraint we've expressed requires that something like T source(); T b = source(); be valid -- certainly something that seems sensible.
Under those constraints, then not much changes with C++11. Of particular note is that such a (pathological) type is irregular:
struct irregular {
irregular() = default;
irregular(irregular const&) = default;
irregular& operator=(irregular const&) = default;
irregular(irregular&&) = delete;
irregular& operator=(irregular&&) = delete;
};
because something like irregular a; irregular b = a; is valid while irregular source(); irregular b = source(); isn't. It's a type that is somewhat copyable (resp. copy assignable), but not quite enough. [ This has been considered somewhat of a defect and is slated to be changed for C++1y, where such a type will in fact be copyable. ]
Going further, for the post-condition that a copy must be equivalent in some sense to the original (or, for rvalues, to the original before the copy) to hold, a move special member can only ever be an 'optimization' of the respective copy special member. Another way to put it is that copy semantics are a refinement of move semantics. This means that the assertion must hold in the following:
T a;
T b = a;
T c = std::move(a);
assert( b == c );
I.e. whether we arrived there via a copy 'request' (that is, an expression involving an lvalue source) or via a move request (an expression involving an rvalue source), we must have the same result regardless of what 'actually' happened (whether a copy special member or move special member was involved, if at all).
Of interest is the fact that the traits such as std::is_copy_constructible used to be called std::has_copy_constructor, but were renamed to put the emphasis on expressions rather than intrinsic properties: something like std::is_copy_constructible<int>::value && std::is_move_assignable<int>::value is true regardless of the fact that int has no constructors or assignment operators.
I advise you to really do generic programming by expressing constraints on the expression level because e.g. the presence or absence of a move constructor is neither sufficient nor necessary for a type to be copy constructible.
add move assignment and a move copy constructor, along with all the other operators of built in types and I'd say you have it, according to Stepanov's paper.
Somewhat related to Why is copy constructor called instead of conversion constructor?
There are two syntaxes for initialization, direct- and copy-initialization:
A a(b);
A a = b;
I want to know the motivation for them having different defined behavior. For copy initialization, an extra copy is involved, and I can't think of any purpose for that copy. Since it's a copy from a temp, it can and probably will be optimized out, so the user can't rely on it happening - ergo the extra copy itself isn't reason enough for the different behavior. So... why?
Only a speculation, but I am afraid it will be hard to be more certain without Bjarne Stroustrup confirming how it really was:
It was designed this way because it was assumed such behaviour will be expected by the programmer, that he will expect the copy to be done when = sign is used, and not done with the direct initializer syntax.
I think the possible copy elision was only added in later versions of the standard, but I am not sure - this is something somebody may be able to tell certainly by checking the standard history.
Since it's a copy from a temp, it can and probably will be optimized out
The keyword here is probably. The standard allows, but does not require, a compiler to optimize the copy away. If some compilers allowed this code (optimized), but others rejected it (non-optimized), this would be very inconsistent.
So the standard prescribes a consistent way of handling this - everyone must check that the copy constructor is accessible, whether they then use it or not.
The idea is that all compilers should either accept the code or reject it. Otherwise it will be non-portable.
Another example, consider
A a;
B b;
A a1 = a;
A a2 = b;
It would be equally inconsistent to allow a2 but forbid a1 when As copy constructor is private.
We can also see from the Standard text that the two methods of initializing a class object were intended to be different (8.5/16):
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. 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 cv-unqualified version of the destination type. The temporary is a prvalue. 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.
A difference is that the direct-initialization uses the constructors of the constructed class directly. With copy-initialization, other conversion functions are considered and these may produce a temporary that has to be copied.
Take the following example:
struct X
{
X(int);
X(const X&);
};
int foo(X x){/*Do stuff*/ return 1; }
X x(1);
foo(x);
In the compilers I tested, the argument to foo was always copied even with full optimization turned on. From this, we can gather that copies will not/must not be eliminated in all situations.
Now lets think from a language design perspective, imagine all the scenarios you would have to think about if you wanted to make rules for when a copy is needed and when it isn't. This would be very difficult. Also, even if you were able to come up with rules, they would be very complex and almost impossible for people to comprehend. However, at the same time, if you forced copies everywhere, that would be very inefficient. This is why the rules are the way they are, you make the rules comprehensible for people to understand while still not forcing copies to be made if they can be avoided.
I have to admit now, this answer is very similar to Suma's answer. The idea is that you can expect the behavior with the current rules, and anything else would be too hard for people to follow.
Initialization of built-in types like:
int i = 2;
is very natural syntax, in part due to historical reasons (remember your high school math). It is more natural than:
int i(2);
even if some mathematicians may argue this point. After all, there is nothing unnatural in calling a function (a constructor in this case) and passing it an argument.
For built-in types these two types of initialization are identical. There is no extra copy in the former case.
That is the reason for having both types of initialization and originally there was no specific intention to make them behave differently.
However, there are user-defined types and one of the stated goals of the language is to allow them to behave as built-in types as closely as possible.
Thus, copy construction (taking input from some conversion function, for example) is the natural implementation of the first syntax.
The fact that you may have extra copies and that they may be elided is an optimization for user-defined types. Both copy elision and explicit constructors came much later into the language. It is not surprising that standard allows optimizations after a certain period of use. Also, now you can eliminate explicit constructors from the overload resolution candidates.