ctor: Why does 'explicit' prevent assignment construction? - c++

I've got a class ByteArray defined like this:
class ByteArray
{
public:
explicit ByteArray( unsigned int uiSize = 0 );
explicit ByteArray( const char * ucSource );
ByteArray( const ByteArray & other );
ByteArray & operator=( const char * ucSource );
ByteArray & operator=( const ByteArray & other );
}
While almost everything works, constructing a ByteArray by assignment doesn't compile.
ByteArray ba1( 5 ); // works
ByteArray ba2( ba1 ); // works
ByteArray ba3( "ABC" ); // works
ByteArray ba4; // works
ba4 = "ABC"; // works
ByteArray ba5 = "ABC"; // <<<----- doesn't compile!
The compiler gives me a Cannot convert 'const char *' to 'ByteArray'.
However, the "assignment-constructor" should be the same as the copy-constuktor, ie. the ba5 line should compile just as the ba3 line--- in contrast to the construction of ba4 and subsequent assignment. So, I'm not quite sure what problem the compiler is having.
I know that a solution would be to remove the explicit in front of the 3rd ctor. I would rather understand what's going on first, though...
Edit:
The answer states it nicely: ByteArray ba5 = "ABC"; would get compiled as ByteArray ba5( ByteArray("ABC") ); --- NOT as ByteArray ba5("ABC"); as I thought it would. Obvious, but sometimes you need someone to point it out. Thanks everyone for your answers!
Why use 'explicit' anyway? Because there is an ambiguity between unsigned int and const char *. If I call ByteArray ba( 0 ); both ctors would be able to handle that, so I need to forbid the implicit conversion and make it explicit.

ByteArray ba5 = "ABC"; is copy initialization, not assignment.
Think of it as
ByteArray ba5(ByteArray("ABC"));
or at least that's what the compiler sees. It's illegal in your case because of the explicit property of the constructor - the compiler wants to use that conversion constructor to perform copy initialization, but it can't because you didn't explicitly use it.

If you don't use explicit keyword, compiler is allowed to convert initialization using = (copy initialization) into initialization using constructor. But sometimes you don't want this behavior and thus you use explicit keyword to avoid this conversion. So you are actually getting the intended result.
Some examples that can be restricted with explicit constructor are
explicit T(const other_type &other);
T object = other;
f(other); // if f recieves object by value
return other; // if function returns object by value
catch ( T other);
T array [ N ] = { other };

C++11 12.3.1/2 "Conversion by constructor" says:
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used.
The form:
ByteArray ba5 = "ABC";
Is copy-initialization (per 8.5/14), not direct-initialization (ByteArray ba3( "ABC" )) (per 8.5/15), so an explicit constructor cannot be used.

C++ has two types of initialization, copy initialization and
direct initialization. In the case of copy initialization, the
formal rules say that the copy constructor should be used; if
the expression doesn't have the correct type, it will be
converted. (The compiler is allowed to elide the extra copy,
but it still must ensure that the code is legal without the
eliding.) The initialization signaled by an = sign (which in
this case is not the assignment operator) uses copy
initialization, as does passing a parameter or returning
a value, or throwing or catching an exception. The
initialization which is marked by parentheses or braces (in
C++11) is direct initialization, as is the initialization in
a new expression, base and member initialization, and the
various explicit conversions (static_cast, etc.).
For actual assignment, of course, the rules are those of
a function call (which is what it is—no new variable is
constructed). In your case, ba4 = "ABC"; works because you
have an assignment operator which takes a char const*; no
implicit conversion is necessary.
(And while I'm at it: I'd avoid explicit on a copy
constructor. I'm not sure exactly what it means, and I'm not
sure that anyone else is either—the purpose of explicit
is to prevent the constructor from being used in implicit
conversions, and of course, the copy constructor can't be used
in any conversions anyway.)

The answers provided so far give insight into why the code doesn't work. But that doesn't explain why it shouldn't work.
Copy initialization is a way of initialising variables. Explicit is a way of ensuring that implicit conversions don't happen in construction or assignment. In my experience copy initialization always behaves the same way as direct initialization (though I don't know if this is guarenteed).
Why should the presence of "explicit" stop copy initialization being valid? Or, looking at it the other way, how can I get copy initialization to work with explict constructors? If not possible then surely this is a defect in the language specification - there doesn't seem to be any disadvantage to enabling it, providing the relevant functions are declared and defined.

Related

Why does C++ implicit casting work, but explicitly casting throws an error (specific example)?

I was trying to overload the casting operator in C++ for practice, but I encountered a problem and I can't figure out the issue. In the example, you can implicitly cast fine, but it causes an error when you try to explicitly cast.
struct B
{
B() = default;
B( B& rhs ) = default;
};
struct A
{
operator B()
{
return B();
}
};
int main()
{
A a;
B example = a; //fine
B example2 = static_cast<B>(a); //error
}
The error is:
error C2440: 'static_cast': cannot convert from 'A' to 'B'
message : No constructor could take the source type, or constructor overload resolution was ambiguous
The problem only appears if you define the copy constructor in the B structure. The problem goes away, though, if you define the move constructor, too, or make the copy constructor take in a const B& ( B( const B& rhs ) ).
I think the problem is that the explicit cast is ambiguous, but I don't see how.
I was looking at a similar problem, here, but in that case I could easily see how the multiple options for casting led to ambiguity while I can't here.
static_cast<B>(a);
This expression is an rvalue, more loosely described as a temporary value.
The B class has no suitable constructor. The B( B &rhs) constructor is not suitable, mutable lvalue references don't bind to temporaries, hence the compilation failure.
Before C++17:
Both lines do effectively the same thing. B example = /*...*/; is copy-initialization which will first convert the right-hand side to the type B if necessary in some suitable manner, resulting in a temporary object from which example is then initialized by choosing a suitable constructor (typically a copy or move constructor).
Because A is not related to B via inheritance, there is no way to bind a directly to the rhs parameter in the (copy) constructor of B. There must first be a conversion from a to a B which is possible implicitly or explicitly via the conversion function you defined.
The result of the conversion will always be a prvalue. A prvalue however can not be bound to a non-const lvalue reference. Therefore it doesn't help that the conversion is possible, the B( B& rhs ) constructor overload is still not viable.
A constructor B( const B& rhs ) (which is also the signature of the implicitly-declared copy constructor if you don't declare one yourself) would however be viable because the prvalue resulting from the conversion can be bound to a const lvalue reference.
Therefore both lines are ill-formed, before C++17.
Since C++17 the mandatory copy elision rules state that initialization of a variable of a type B from a prvalue of type B is done exactly as if the variable was directly initialized by the initializer of the prvalue, so that no overload resolution on the constructor will be performed at all. Whether there is a copy constructor or how exactly it is declared is then irrelevant. Similarly the rules for copy-initialization from a non-reference compatible lvalue such as a have been updated to have the same effect.
So since C++17 both lines will compile.
If you are using MSVC in a default configuration with the language standard set below C++17, the first line will compile, but that is not standard-conforming. By default when set to language versions below C++20 MSVC uses a slight dialect of C++ which is not conforming to the standard in some areas. You can set the standards conformance mode (/permissive- flag) to set MSVC to behave (more) standard-conforming.

Why is std::string s = "123" considered copy-initialisation when no copy is involved?

Why is this way of initialisating a std::string:
std::string s = "123";
considered a copy initialisation, when no copy whatsoever actually occurs?
In the above case, there is no ambiguity that the compiler will see that there is a std::string constructor that takes a char const *, and consequently what happens here is construction of a std::string object via implicit conversion of char const * to std::string. This is such a clear-cut scenario. It's simply calling a std::string(const char *) constructor once, plain and simple. So simple there is even nothing to talk about regarding optimisations such as copy elision and move.
Now, the problem is, I never had any confusion about object initialisation via implicit conversion (i.e. Class a = expression) until I started coming across literature declaring that initialisation by = is "copy initialisation". Even the main man, Bjarne Stroustrup himself, refers to this form of initialisation as "copy initialisation".
At this point, I feel that I may be misunderstanding something.
So, why is initialisation by = considered copy-initialisation when clearly this is not the case if implicit conversion is allowed?
The term copy-initialization is simply used for an initializing syntax of the form:
T object = other;
where one of the effects of this initialization is:
If T is a class type, and the cv-unqualified version of the type of other is not T or derived from T, or if T is non-class type, but the type of other is a class type, user-defined conversion sequences that can convert from the type of other to T (or to a type derived from T if T is a class type and a conversion function is available) are examined and the best one is selected through overload resolution.
So for the expression:
std::string s = "123";
the implicit constructor that takes a const char * is used to construct the std::string.
So even though it has the term copy in it, copy-initialization does not mean there is an actual copy involved, it's only called that because the syntax makes it appear like a copy is happening.
The reason it is called copy initialization is because before C++11, that is what literally had to be done, per the rules of the language. When you have
T t = u;
if u is a T, then you call the copy constructor. So calling it copy initialization for that case makes sense
if u is not a T then [dcl.init]/15 bullet 7 came into play (from the C++03 draft) and that has
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 an rvalue. 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.
emphasis mine
which states that a temporary is created, and that temporary is used to initialize them object. Yes, it says this can be avoided in certain cases, but those section only allow the optimization, it is not mandated to happen.
So, again we are making a copy, so copy initialization makes sense.
With C++17 this is no longer the case and your guaranteed that no copy will exist, but were stuck with the name at this point.

Implicit VS Explicit Conversion

The C++ Standard Library by Nicolai M. Josuttis states:
There is a minor difference between
X x;
Y y(x) //explicit conversion
and
X x;
Y y = x; //implicit conversion
Following to say: "The former creates a new object of type Y by using an explicit conversion from type X, whereas the latter creates a new object of type Y by using an implicit conversion."
I'm a little confused about the concepts of explicit vs implicit conversion I guess. In both cases you're taking an X and pushing it into a Y per se - one uses a Y's constructor and one uses the assignment operator though.
What's the difference in how the conversion is treated in these two cases, what makes it explicit/implicit, and how does this tie into making a class constructor defined with the "explicit" key word, if at all?
one uses a Y's constructor and one uses the assignment operator though.
Nope. In the second case it's not an assignment, it's an initialization, the assignment operator (operator=) is never called; instead, a non-explicit one-parameter constructor (that accepts the type X as a parameter) is called.
The difference between initialization and assignment is important: in the first case, a new object is being created, and it starts its life with the value that it is being initialized with (hence why a constructor is called), while assignment happens when an object is assigned (~copied) to an object that already exists and already is in a definite state.
Anyway, the two forms of initialization that you wrote differ in the fact that in the first case you are explicitly calling a constructor, and thus any constructor is acceptable; in the second case, you're calling a constructor implicitly, since you're not using the "classical" constructor syntax, but the initialization syntax.
In this case, only one-parameter constructors not marked with explicit are acceptable. Such constructors are called by some people "converting" constructors, because they are involved in implicit conversions.
As specified in this other answer, any constructor not marked as explicit can take part in an implicit conversion for e.g. converting an object passed to a function to the type expected by such function. Actually, you may say that it's what happens in your second example: you want to initialize (=create with a value copied from elsewhere) y with x, but x first has to be converted to type Y, which is done with the implicit constructor.
This kind of implicit conversion is often desirable: think for example to a string class that has a converting (i.e. non-explicit) constructor from a const char *: any function that receives a string parameter can also be called with a "normal" C-string: because of the converting constructor the caller will use C-strings, the callee will receive its string object.
Still, in some cases one-parameters constructors may not be appropriate for conversion: usually this happens when their only parameter is not conceptually "converted" to the type of the object being created, but it is just a parameter for the construction; think for example about a file stream object: probably it will have a constructor that accepts the name of the file to open, but it makes no sense to say that such string is "converted" to a stream that works on that file.
You can also find some more complex scenarios where these implicit conversions can completely mess-up the behavior that the programmer expects from overload resolution; examples of this can be found in the answers below the one I linked above.
More simply, it can also happen that some constructors may be very heavyweight, so the class designer may want to make sure that they are invoked explicitly. In these cases, the constructor is marked as explicit, so it can be used only when called "explicitly as a constructor" and doesn't take part in implicit conversions.
one uses the assignment operator though
No, it does not. It calls the constructor directly.
The reason why one is explicit and one is implicit is because implicit conversions can happen when you don't want them to. Explicit ones cannot. The easiest example of this is bool.
Let's say that you invent some type which can be either true or false- like a pointer. Then let's further say that you decide that in order to make life easier for your users, you let it convert to bool implicitly. This is great- right up until the point where one of your users does something dumb.
int i = 0;
i = i >> MyUDT();
Oh wait- why does that even compile? You can't shift a MyUDT at all! It compiles because bool is an integral type. The compiler implicitly converted it to a bool, and then to something that can be shifted. The above code is blatantly dumb- we only want people to be able to convert to a bool, not a bool and anything else that a bool might want to do.
This is why explicit conversion operators were added to C++0x.
The first form is direct initialization. The second is copy initialization.
Copy initialization implicitly calls a converting constructor or conversion operator and then explicitly calls a copy constructor (the copy constructor call may be elided, but an accessibility check must still be performed).
Consider a third possibility, which is copy-initialization, but the conversion is explicit:
Y y = Y(x);
or
Y y = (Y)x;
The Implicit casting doesn't require any casting operator. This casting is normally used when converting data from smaller integral types to larger or derived types to the base type.
int iVal = 100;
double dVal = iVal;
The explicit converting constructor is preferred to the implicit conversion operator because in the latter case there is an additional call to the copy constructor.Implicit and Explicit converstions

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.

'Bracket initializing'. (C++)

I'm learning C++ at the moment, C++ Primer plus. But I just felt like checking out the cplusplus website and skip a little forward to file handling.
I pretty much know the basics of file handling coming from java, php, visual basic. But I came across a pretty weird line.
ostream os(&fb);
fb represents a filebuf. I just don't get the syntax of this, but I can figure out that it's the same as:
ostream os = &fb;
But I never really read about this way of initializing variables.
So I'm wondering. Am I just senseless and missing out a real useful feature the entire time? Is this way of initializing just old? Is it something different?
Thanks in advance.
Both forms perform initialization. The first syntax (with ()) is called direct-initialization syntax. The second syntax (with =) is called copy-initialization syntax. They will act same in most real-life cases, but there are indeed differences between the two.
In situations when types on the left-hand side (LHS) and right-hand side (RHS) are identical (ignoring any const/volatile qualifiers), both are indeed exactly the same. The language standard explicitly states that in this case the = form is equivalent to () form.
But when the types are different (and the LHS type is a class type), these two forms will generally work differently.
The copy-initialization form works as follows: convert the RHS value to the temporary object of LHS type (by any means possible: standard conversion, conversion operator, conversion constructor). And then use the copy constructor of the LHS class to copy the temporary object to the LHS object.
The direct initialization form work as follows: just consider all constructors of LHS and choose the most appropriate one by using overload resolution.
You can immediately notice that the copy-initialization syntax unconditionally uses the copy constructor (the copying and the intermediate temporary can be optimized away, but conceptually they are there). If the LHS class has no accessible copy-constructor, the copy-initialization unconditionally becomes ill-formed, while direct-initialization might still work.
Also, the keyword explicit applied to certain constructor will affect which form of initialization is available for which combinations of types.
A small program to see when copy constructor is called and when overloaded assignment operator function is called:
#include <iostream>
using namespace std;
class test
{
public:
// default constructor.
test()
{
cout<<"Default Ctor called"<<endl;
}
// copy constructor.
test(const test& other)
{
cout<<"Copy Ctor called"<<endl;
}
// overloaded assignment operator function.
test& operator=(const test& other)
{
cout<<"Overload operator function called"<<endl;
return *this;
}
};
int main(void)
{
test obj1; // default constructor called.
test obj2 = obj1; // copy constructor called.
test obj3(obj2); // again copy constructor called.
obj1 = obj2; // overloaded assignment operator function.
return 0;
}
Output:
Default Ctor called
Copy Ctor called
Copy Ctor called
Overload operator function called
So in your case, the copy constructor of ostream is called in both the occasions.
Perhaps you should read this and this
One important benefit of function-call initialization is that they also work with constructors that take multiple arguments. For example, an fstream constructor can take two parameters:
std::fstream file("filename", ios_base::out);
Until C++0x uniform initialization is widely available, function-call initialization is the only way to handle multiple argument constructors.
From my understanding, &var is a alias for the var variable and doesn't matter which one you use.
--------Addition -----------------
The below code is taken from Stroustrup book.From this its clear that both are alias to the same variable. It also says as below.
"The semantics of argument passing are defined to be those of initialization, so when called, increment's argument aa became another name of x." Thats why I refered to &x to be alias of x.
void increment(int& aa) { aa++; }
void f()
{
int x = 1;
increment(x);
}