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.
Related
I have heard that C++ has something called "conversion constructors" or "converting constructors". What are these, and what are they for? I saw it mentioned with regards to this code:
class MyClass
{
public:
int a, b;
MyClass( int i ) {}
}
int main()
{
MyClass M = 1 ;
}
The definition for a converting constructor is different between C++03 and C++11. In both cases it must be a non-explicit constructor (otherwise it wouldn't be involved in implicit conversions), but for C++03 it must also be callable with a single argument. That is:
struct foo
{
foo(int x); // 1
foo(char* s, int x = 0); // 2
foo(float f, int x); // 3
explicit foo(char x); // 4
};
Constructors 1 and 2 are both converting constructors in C++03 and C++11. Constructor 3, which must take two arguments, is only a converting constructor in C++11. The last, constructor 4, is not a converting constructor because it is explicit.
C++03: §12.3.1
A constructor declared without the function-specifier explicit that can be called with a single parameter specifies a conversion from the type of its first parameter to the type of its class. Such a constructor is called a converting constructor.
C++11: §12.3.1
A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.
Why are constructors with more than a single parameter considered to be converting constructors in C++11? That is because the new standard provides us with some handy syntax for passing arguments and returning values using braced-init-lists. Consider the following example:
foo bar(foo f)
{
return {1.0f, 5};
}
The ability to specify the return value as a braced-init-list is considered to be a conversion. This uses the converting constructor for foo that takes a float and an int. In addition, we can call this function by doing bar({2.5f, 10}). This is also a conversion. Since they are conversions, it makes sense for the constructors they use to be converting constructors.
It is important to note, therefore, that making the constructor of foo which takes a float and an int have the explicit function specifier would stop the above code from compiling. The above new syntax can only be used if there is a converting constructor available to do the job.
C++11: §6.6.3:
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
§8.5:
The initialization that occurs [...] in argument passing [...] is called copy-initialization.
§12.3.1:
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.
Converting implicitly with converting constructor
Let's make the example in the question more complex
class MyClass
{
public:
int a, b;
MyClass( int i ) {}
MyClass( const char* n, int k = 0 ) {}
MyClass( MyClass& obj ) {}
}
First two constructors are converting constructors. The third one is a copy constructor, and as such it is another converting constructor.
A converting constructor enables implicit conversion from argument type to the constructor type. Here, the first constructor enables conversion from an int to an object of class MyClass. Second constructor enables conversion from an string to an object of class MyClass. And third... from an object of class MyClass to an object of class MyClass !
To be a converting constructor, constructor must have single argument (in the second one, second argument has one default value) and be declared without keyword explicit.
Then, initialization in main can look like this:
int main()
{
MyClass M = 1 ;
// which is an alternative to
MyClass M = MyClass(1) ;
MyClass M = "super" ;
// which is an alternative to
MyClass M = MyClass("super", 0) ;
// or
MyClass M = MyClass("super") ;
}
Explicit keyword and constructors
Now, what if we had used the explicit keyword ?
class MyClass
{
public:
int a, b;
explicit MyClass( int i ) {}
}
Then, compiler would not accept
int main()
{
MyClass M = 1 ;
}
since this is implicit conversion. Instead, have to write
int main()
{
MyClass M(1) ;
MyClass M = MyClass(1) ;
MyClass* M = new MyClass(1) ;
MyClass M = (MyClass)1;
MyClass M = static_cast<MyClass>(1);
}
explicit keyword is always to be used to prevent implicit conversion for a constructor and it applies to constructor in a class declaration.
A conversion constructor is a single-parameter constructor that is declared without the function specifier explicit . The compiler uses conversion constructors to convert objects from the type of the first parameter to the type of the conversion constructor's class.
I have heard that C++ has something called "conversion constructors" or "converting constructors". What are these, and what are they for? I saw it mentioned with regards to this code:
class MyClass
{
public:
int a, b;
MyClass( int i ) {}
}
int main()
{
MyClass M = 1 ;
}
The definition for a converting constructor is different between C++03 and C++11. In both cases it must be a non-explicit constructor (otherwise it wouldn't be involved in implicit conversions), but for C++03 it must also be callable with a single argument. That is:
struct foo
{
foo(int x); // 1
foo(char* s, int x = 0); // 2
foo(float f, int x); // 3
explicit foo(char x); // 4
};
Constructors 1 and 2 are both converting constructors in C++03 and C++11. Constructor 3, which must take two arguments, is only a converting constructor in C++11. The last, constructor 4, is not a converting constructor because it is explicit.
C++03: §12.3.1
A constructor declared without the function-specifier explicit that can be called with a single parameter specifies a conversion from the type of its first parameter to the type of its class. Such a constructor is called a converting constructor.
C++11: §12.3.1
A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.
Why are constructors with more than a single parameter considered to be converting constructors in C++11? That is because the new standard provides us with some handy syntax for passing arguments and returning values using braced-init-lists. Consider the following example:
foo bar(foo f)
{
return {1.0f, 5};
}
The ability to specify the return value as a braced-init-list is considered to be a conversion. This uses the converting constructor for foo that takes a float and an int. In addition, we can call this function by doing bar({2.5f, 10}). This is also a conversion. Since they are conversions, it makes sense for the constructors they use to be converting constructors.
It is important to note, therefore, that making the constructor of foo which takes a float and an int have the explicit function specifier would stop the above code from compiling. The above new syntax can only be used if there is a converting constructor available to do the job.
C++11: §6.6.3:
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
§8.5:
The initialization that occurs [...] in argument passing [...] is called copy-initialization.
§12.3.1:
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.
Converting implicitly with converting constructor
Let's make the example in the question more complex
class MyClass
{
public:
int a, b;
MyClass( int i ) {}
MyClass( const char* n, int k = 0 ) {}
MyClass( MyClass& obj ) {}
}
First two constructors are converting constructors. The third one is a copy constructor, and as such it is another converting constructor.
A converting constructor enables implicit conversion from argument type to the constructor type. Here, the first constructor enables conversion from an int to an object of class MyClass. Second constructor enables conversion from an string to an object of class MyClass. And third... from an object of class MyClass to an object of class MyClass !
To be a converting constructor, constructor must have single argument (in the second one, second argument has one default value) and be declared without keyword explicit.
Then, initialization in main can look like this:
int main()
{
MyClass M = 1 ;
// which is an alternative to
MyClass M = MyClass(1) ;
MyClass M = "super" ;
// which is an alternative to
MyClass M = MyClass("super", 0) ;
// or
MyClass M = MyClass("super") ;
}
Explicit keyword and constructors
Now, what if we had used the explicit keyword ?
class MyClass
{
public:
int a, b;
explicit MyClass( int i ) {}
}
Then, compiler would not accept
int main()
{
MyClass M = 1 ;
}
since this is implicit conversion. Instead, have to write
int main()
{
MyClass M(1) ;
MyClass M = MyClass(1) ;
MyClass* M = new MyClass(1) ;
MyClass M = (MyClass)1;
MyClass M = static_cast<MyClass>(1);
}
explicit keyword is always to be used to prevent implicit conversion for a constructor and it applies to constructor in a class declaration.
A conversion constructor is a single-parameter constructor that is declared without the function specifier explicit . The compiler uses conversion constructors to convert objects from the type of the first parameter to the type of the conversion constructor's class.
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".
What's the difference between the following two declarations, assuming I have not specified a copy constructor and operator= in class Beatle?
Beatle john(paul);
and
Beatle john = paul;
Edit:
In objects assignment, the operator = implicitly calls the copy constructor unless told otherwise?
They're different grammatical constructions. The first one is direct initialization, the second is copy initialization. They behave virtually identically, only that the second requires a non-explicit constructor.*
Neither has anything to do with the assignment operator, as both lines are initializations.
To wit: const int i = 4; is fine, but const int i; i = 4; is not.
*) More accurately: The second version does not work if the relevant constructor is declared explicit. More generally, thus, direct-initialization affords you one "free" conversion:
struct Foo { Foo(std::string) {} };
Foo x("abc"); // OK: char(&)[4] -> const char * -> std::string -> Foo
Foo y = "abd"; // Error: no one-step implicit conversion of UDTs
To address your edit: To understand the assignment operator, just break it up into parts. Suppose Foo has the obvious operator=(const Foo & rhs). We can say x = y;, which just calls the operator directly with rhs being y. Now consider this:
x = "abc"; // error, no one-step implicit conversion
x = std::string("abc"); // fine, uses Foo(std::string), then copy
x = Foo("abc"); // fine, implicit char(&)[4] -> const char* -> std::string, then as above
First is Direct Initialization & second is Copy Initialization.
Direct initialization means the object is initialized using a single (possibly conversion) constructor, and is equivalent to the form T t(u);:
U u;
T t1(u); // calls T::T( U& ) or similar
Copy initialization means the object is initialized using the copy constructor, after first calling a user-defined conversion if necessary, and is equivalent to the form T t = u;:
T t2 = t1; // same type: calls T::T( T& ) or similar
T t3 = u; // different type: calls T::T( T(u) )
// or T::T( u.operator T() ) or similar
Copy Initialization does not work if the constructor is declared explicit.
References:
This entry in Herb Sutter's GOTW should be a good read.
To answer your edited Question:
= has a different meaning in depending on how it is used.
If = is used in an expression in which the object is being created and initialized at the same time, then = is not treated as Assignment Operator but as Copy Initialization.
If = is being used to assign one object to another, after the object has been created then it results in call to Assignment Operator.
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;
}