I read the C++ Primer and it is said when we use a '=' operator, or when a function parameter is a value of a class type or a function return type is a value of a class type, c++ will use what's called "copy initialization", which different with "direct initialization" that find the corresponding constructor based on parameter, "copy initialization" will use the "copy constructor". So in the following code, when I supplies "hello" as the argument to Foo, it should use the copy constructor, which is string (const string& str) to initialize the parameter. But my argument is "hello", which is a const char *, so what actually happened for the const char * to become a valid argument for the const string & parameter? Could some one give more detailed information? Thanks!
void Foo(string str){}
int main() {
Foo("hello");
}
The initialization of str in the above code has the same semantics as the following:
string str = "hello";
This is indeed what is called copy initialization. Assuming that string is std::string, there exists a constructor taking a single argument of type const char*, that is, string::string(const char*). This constructor will be invoked for the initialization. Calling this constructor creates a temporary of type string. Then the copy or move constructor of string is called to initialize str from the temporary. Typically, the extra copy or move is elided, and neither the copy nor move constructor is called at all, despite the name "copy initialization".
Related
In the class defined as below named foo
class foo{
private:
string str;
public:
foo operator = (string s){
str = s;
}
};
int main(){
foo a = "this initialization throwing an error";
foo b;
b = "but assignment after declaration is working fine";
}
error: conversion from 'const char [38]' to non-scalar type 'foo' requested
The above error is caused only when I am assigning value to the object instance with declaration but if I am assigning separately from declaration then the overloaded equal = operator is working fine.
I want in any method to assign an string to the object with equal operator
and as a declaration like foo a = "abcd";
When you have
type name = something;
You are not doing assignment but instead you have copy-initialization (do not that there can be a move even though it is called copy). This means that you need a constructor in your class whose parameter matches that of the thing on the right hand side of the =.
In this case you do not have a constructor that takes a std::string, const char* or const char[] so the compiler is unable to construct an instance there.
The reason the second case works is that
b = "but assignment after declaration is working fine";
Is not trying to construct anything and therefore it calls the assignment operator which does work because
"but assignment after declaration is working fine"
can be converted to a std::string.
If you want your class to be constructable from a string literal or cstring then you can add a constructor to it in the form of
foo(const char* str) : str(str) {}
What you have in the first case is called copy initialization and as stated in documentation:
The equals sign, =, in copy-initialization of a named variable is not
related to the assignment operator. Assignment operator overloads have
no effect on copy-initialization.
Hense the error. So possible solutions:
First you can create a constructor, that accepts std::string:
foo( const std::string &s );
that will allow you to create foo by this:
foo f( "abc" );
or even this:
foo f = foo( "abc" );
but your statement will still fail, because of:
in addition, the implicit conversion in copy-initialization must
produce T directly from the initializer, while, e.g.
direct-initialization expects an implicit conversion from the
initializer to an argument of T's constructor.
so to make your statement work as it is you need to add this ctor:
foo( const char *str );
Note: when you add proper ctor you do not need to define assignment operator - conversion constructor will be used. And your overloaded assignment operator should return reference to foo.
When you create an object, the code initializes it with a constructor, even when the creation uses an = sign:
struct S {
S(int);
};
S s = 3; // initialization, not assignment
Formally, that initialization uses S(int) to create a temporary object of type S, and then uses the copy constructor to construct the object s from the temporary object.
That's vastly different from assignment, which deals with an already existing object:
S s1;
s1 = 3; // assignment
Here, the assignment would use the assignment operator if S defined one. That's why the b = line in the original code works.
Your problem is that
foo a = "initialization string";
attempts to create an object of type foo however there is no constructor defined that accepts a parameter of type string.
You could define one like this:
foo(const std::string& s) : str(s) {}
foo(const char* s) : str(s) {}
For starters though the compiler does not issue a diagnostic message nevertheless the assignment operator in the class definition
class foo{
private:
string str;
public:
foo operator = (string s){
str = s;
}
};
is invalid because it returns nothing while it has return type foo.
You should it write like
foo & operator = ( const std::string &s){
str = s;
return *this;
}
As you declared neither the default constructor nor the copy constructor then the compiler implicitly declared them instead of you.
So in fact your class has only two constructor. The default constructor that has the following declaration
foo();
and the copy constructor that has the following declaration
for( const foo & );
In this statement
foo a = "this initialization throwing an error";
the compiler assumes that in the right side there is an object of type foo and you are going to use it to create the object a. That is in this declaration the compiler tries to apply the implicitly created copy constructor. To so so it need to convert the string literal to an object of type foo. However the class does not have a conversion constructor that is a constructor with a parameter that can accept the string literal. As result the compiler issues the error
error: conversion from 'const char [38]' to non-scalar type 'foo'
requested
const char[33] is the type of the string literal in the right side of the declaration.
In this code snippet
foo b;
b = "but assignment after declaration is working fine";
at first there is created the object b of the type foo using the implicitly defined by the compiler default constructor and then in the second statement there is used the assignment operator that is explicitly defined in the class. As it follows from the definition of the operator it assigns data member str with a temporary object of type std::string that is constructed from the used string literal. It is possible because the class std::string has a corresponding conversion constructor.
The following quote from my C++ book:
When we use direct initialization, we are asking the compiler to use ordinary function matching to select the constructor that best matches the arguments
we provide. When we use copy initialization, we are asking the compiler to copy
the right-hand operand into the object being created, converting that operand if
necessary.
For me this bolded bit produces a bit of ambiguity. It makes it sound like the right-hand operand is converted to the class-type and then the copy-constructor is used, for instance;
string s = "hello";
would become...
string s = string("hello");
which uses the copy constructor. If this is true then my test program;
#include <iostream>
using namespace std;
class A{
public:
A(const A& b): data(b.data) { cout << "The first way" << endl;}
A(const char* c): data(c) { cout << "The second way" << endl;}
string data;
};
int main(){
A first("hello");
A second = "sup";
}
Should be producing "The second way, The second way, The first way". However it instead prints "The second way, The second way". From this I would've concluded it is using the const char* constructor and not the copy-constructor. I would be fine with this except later it says...
During copy initialization, the compiler is permitted (but not obligated) to skip the copy/move constructor and create the object directly. That is, the compiler is permitted to rewrite
string null_book = "9-999-99999-9";
into
string null_book("9-999-99999-9");
However, even if the compiler omits the call to the copy/move constructor, the
copy/move constructor must exist and must be accessible (e.g., not private) at that point in the program.
I'm not sure why the copy constructor even needs a mention in these examples, doesn't
string null_book = "9-999-99999-9"
always implicitly mean that the const char* constructor is being used anyway? Indeed it makes less sense to me that the copy-constructor needs to be defined in order for the above to work. But alas, if I put the "const A&" constructor as private (the rest public) then my program will not run. Why must the copy-constructor be defined for implicit conversions that don't even involve it? What constructor does "string null_book = "9-999-99999-9"" use?
string null_book = "9-999-99999-9"; means string null_book = string("9-999-99999-9");.
This uses the const char * constructor to construct a temporary object, and then null_book is copy/move constructed from the temporary object, and then the temporary object is destroyed.
(Copy/move construction means that a move constructor is used if available; otherwise a copy constructor).
However this scenario is also eligible for copy elision. You actually quoted the copy elision specification in your question so I won't repeat it.
The compiler may choose to use the same memory space for both null_book and the temporary object, and omit the calls to the temporary object destructor and the null_book copy/move constructor.
In your case the compiler did indeed choose to do that, which is why you do not see any copy constructor output.
Some compilers allow copy elision to be disabled by a switch, e.g. gcc/clang -fno-elide-constructors.
More info about copy elision
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.
I studied that during initialization of object, for example
string s = "Hello world";
If RHS is implicitly convertible to LHS type object, then Copy Constructor would be called. But I have a friend who is pretty sure that constructor which takes char pointer as an argument would be called.But I told him that constructor with charpointer would be called only in cases as below
string s("Hello world");
Is that correct?
Doing
string s = "Hello world";
is equivalent to
string s( string( "Hello world" ) );
so both the constructor taking char const* and the copy-constructor are called. However, the standard allows copy-elision where the copy-constructor call is elided (not done).
Yes and no. Both are called.
string s = "Hello world";
This is copy initialization. It calls the conversion constructor and constructs a temporary string from "Hellow world" and then uses that temporary with the copy constructor to construct s. (subject to optimizations)
string s("Hello world");
Is direct initialization and calls the conversion constructor directly, constructing s from "Hello world".
Having spent the last couple of days experimenting with C++ operator[] methods, I've come across an anomaly that I can't easily explain. The code below implements a simple test string class, which allows the user to access single characters via its subscript "operator" method. As I would like to differentiate between lvalue and rvalue subscript contexts, the "operator" method returns an instance of the custom "CReference" class as opposed to a standard C++ character reference. Whilst the "CReference" "operator char()" and "operator=(char)" methods appear to handle each rvalue and lvalue context respectively, g++ refuses to compile without the presence of an additional "operator=(CReference&)" method as documented below. Whilst the addition of this method appears to placate some kind of compile-time dependency, it is never actually invoked at run-time during the execution of the program.
As someone who thought they had acquired a fundamental understanding of C++ intricacies, this project has certainly proved to be a humbling experience. If anyone could see their way to enlightening me as to what's going on here, I would be eternally grateful. In the meantime, I'm going to have to pull out the C++ books in order to reconcile the void** between what I know and what I think know.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// Class for holding a reference to type char, retuned by the "operator[](int)"
// method of class "TestString". Independent methods permit differentiation
// between lvalue and rvalue contexts.
class CReference
{
private:
char& m_characterReference;
public:
// Construct CReference from char&
CReference(char& m_initialiser)
: m_characterReference(m_initialiser) {}
// Invoked when object is referenced in a rvalue char context.
operator char()
{
return m_characterReference;
}
// Invoked when object is referenced in a lvalue char= context.
char operator=(char c)
{
m_characterReference = c;
return c;
}
// NEVER INVOKED, but will not compile without! WHY???
void operator=(CReference &p_assignator){}
};
// Simple string class which permits the manipulation of single characters
// via its "operator[](int)" method.
class TestString
{
private:
char m_content[23];
public:
// Construct string with test content.
TestString()
{
strcpy(m_content, "This is a test object.");
}
// Return pointer to content.
operator const char*()
{
m_content[22] = 0;
return m_content;
}
// Return reference to indexed character.
CReference operator[](int index)
{
return m_content[index];
}
};
int main(int argc, char *argv[])
{
TestString s1;
// Test both lvalue and rvalue subscript access.
s1[0] = s1[1];
// Print test string.
printf("%s\n", (const char*)s1);
return 0;
}
The line s1[0] = s1[1]; causes the compiler to generate an implicit copy assignment operator for CReference if you didn't declare one yourself. This causes an error because your class has a reference member, which can't be copied.
If you added an assignment operator that takes a parameter of type const CReference&, it would get called by the assignment.
In your code, you declared a copy assignment operator of type void operator=(CReference &p_assignator). This can't be called because the righthand side of the assignment is a temporary object, which can't be bound to a non-const reference. However, the act of declaring this operator causes the compiler not to try to define an implicit copy assignment operator, and therefore avoids the previous compilation error. Since this operator can't be called, the compiler goes for the other assignment operator that takes a parameter of type char.
What's happening is that without the definition of operator=(CReference&),there are two possible overloads of operator=: the implicitly-defined operator=(const CReference&) and your operator=(char). When it tries to compiled s1[0] = s1[1], it tries to find the best match for operator=, which is the implicitly-defined operator=(const CReference&). But, that isn't allowed to be implicitly-defined, because CReference contains a reference member, so you get an error.
Conversely, when you do define operator=(CReference&), there's no longer an implicitly-defined assignment operator. Sections 12.8 clauses 9-10 of the C++03 standard state:
9) A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.109)
10) If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly.
The implicitly-declared copy assignment operator for a class X will have the form
X& X::operator=(const X&)
if
— each direct base class B of X has a copy assignment operator whose parameter is of type const B&, const volatile B& or B, and
— for all the nonstatic data members of X that are of a class type M (or array thereof), each such class type
has a copy assignment operator whose parameter is of type const M&, const volatile M& or
M.110)
Otherwise, the implicitly declared copy assignment operator will have the form
X& X::operator=(X&)
So, when it tries to compile s1[0] = s1[1], it has two overload choices: operator=(CReference&) and operator=(char). It choose operator=(char) as the better overload because it can't convert a temporary object (the result of s1[1]) into a non-const reference.
I compiled your program on Ideone by removing the overloaded = and it gives the following error:
prog.cpp: In member function ‘CReference& CReference::operator=(const CReference&)’:
prog.cpp:9: error: non-static reference member ‘char& CReference::m_characterReference’, can't use default assignment operator
prog.cpp: In function ‘int main(int, char**)’:
prog.cpp:70: note: synthesized method ‘CReference& CReference::operator=(const CReference&)’ first required here
As the error points out,
s1[0] = s1[1];
needs the copy assignment operator for CReference class.
You have a reference member variable in your CReference class
char& m_characterReference;
For classes with a reference member, you need to provide your own implementation of = operator as a default = cannot infer what to do with the reference member. So you need to provide your own version of the = operator.
So a little bit of speculation here:
With,
s1[0]=s1[1]
Ideally, for CReference& CReference::operator=(const CReference&) to be invoked the argument should be a const CReference& but the argument s1[1] returns a non constant CReference&, so:
why does the compiler asks for CReference& CReference::operator=(const CReference&) in the code sample link I posted &
Why does the error go away if we provide a = operator with a non constant argument, like CReference& CReference::operator=(CReference&)
As it seems in the absence of an explicit non constant argument copy constructor the compiler does some kind of a optimization and treats the argument of s1[1] as an constant thus asking for the constant argument copy assignment operator.
If a non constant argument copy assignment operator is explicitly provided the compiler doesn't perform its optimization but just uses the one that is already provided.
Instead of returning a CReference, you can return a char&. That would remove the need for the CReference class, which is dangerous for the reasons that I included in my comment above. You can have two forms of the operator[], one returning char and one returning char&.
OK, let's see:
s1[0] = s1[1];
s1[0] returns CReference
s1[1] returns CReference
Of course you need an assignment operator that accepts CReference.
Hence your statement in bold letters:
// NEVER INVOKED, but will not compile without! WHY???
void operator=(CReference &p_assignator){}
is incorrect.
I think your
void operator=(CReference &p_assignator){}
is invoked since
// Test both lvalue and rvalue subscript access.
s1[0] = s1[1];
use the function, as both s1[0] and s1[1] return CReference. and not char.
Also you would like to do something like this
CReference & operator=(CReference &p_assignator){}
to handle
s1[0] = s1[1] = s1[2]; //etc...
UPDATED: a thought
Why we need to provide user-defined copy assignment operator?
Answer: I think the default behavior of the implicit assignment operator is to re-assign the referenced member variable to refer to another location instead of the semantic in m_characterReference = p_assignator.m_characterReference;. Reference variable for us is a syntactic sugar but for the compiler it is a constant pointer. Thus, the default assignment tries to re-assign this constant pointer.
The two functions CReference::operator char() and CReference::operator=(char) do not represent lvalue and rvalue.
As previously mentioned, your main mistake is that you declared the copy assignment operator to return void. Thus, it won't be invoked.
For the compiler to execute "s[0] = s[1];" and based on your code, first, s[x] will be converted to CRefernce anonymous objects. Then the compiler search for operator=(). Since you provide operator=(char), the compiler is "smart" and try to fulfill this assignment. So, it will invoke operator char() first to convert right-hand side (which CReference(s[1]) to char, then invoke operator=(char) function. The expression (s[0] = s[1]) becomes of type char.
You can avoid this conversion by modifying your code as
class CReference
{
private:
char& m_characterReference;
public:
// Construct CReference from char&
CReference(char &m_initialiser)
: m_characterReference(m_initialiser) {}
char& operator = (const CReference &r) {
m_characterReference = r.m_characterReference;
return m_characterReference;
}
};
The assignment operator returns char if you would like to keep the type of "s1[0] = s1[1]" as character. You should not worry about conversion from char to CReference (in case of s1[0] = s1[1] = s1[2]) since 1-arg constructor will handle this conversion.
A simple and clear example that represents lvalue and rvalue is
class Point {
public:
int& x() {return x_value; }
int& y() {return y_value; }
private:
int x_value;
int y_value;
};
int main()
{
Point p1;
p1.x() = 2;
p1.y() = 4;
cout << p1.x() << endl;
cout << p1.y() << endl;
}