Strange behaviour with operator[] - c++

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;
}

Related

C++ post-increment: objects vs primitive types

We cannot use pre-increment on rvalues:
int i = 0;
int j = ++i++; // Compile error: lvalue required
If we define a class:
class A
{
public:
A & operator++()
{
return *this;
}
A operator++(int)
{
A temp(*this);
return temp;
}
};
then we can compile:
A i;
A j = ++i++;
What is the different between A object and int data type that
j = ++i++;
compiles with A but not with int?
This happens because when overloaded operators are defined as member functions, they follow some semantics which are more related to calling a member function, not to the behavior of the built-in operator. Note that by default, if we declare a non-static member function like:
class X {
public:
void f();
X g();
};
then we can call it on both lvalue and rvalue class type expressions:
X().f(); // okay, the X object is prvalue
X x;
x.f(); // okay, the X object is lvalue
x.g().f(); // also okay, x.g() is prvalue
When overload resolution for an operator expression selects a member function, the expression is changed to be just a call to that member function, so it follows the same rules:
++A(); // okay, transformed to A().operator++(), called on prvalue
A a;
++a; // okay, transformed to a.operator++(), called on lvalue
++a++; // also technically okay, transformed to a.operator++(0).operator++(),
// a.operator++(0) is a prvalue.
This sort of non-equivalence between built-in operators and overloaded operators also happens with the left subexpression of assignment: the pointless statement std::string() = std::string(); is legal, but the statement int() = int(); is not legal.
But you noted in a comment "I want to design a class that prevents ++a++". There are at least two ways to do that.
First, you could use a non-member operator instead of a member. Most overloaded operators can be implemented as either a member or non-member, where the class type needs to be added as an additional first parameter type of the non-member function. For example, if a has class type, the expression ++a will attempt to find a function as if it were a.operator++() and also a function as if it were operator++(a); and the expression a++ will look for functions for the expressions a.operator++(0) or operator++(a, 0).
(This pattern of trying both ways does not apply to functions named operator=, operator(), operator[], or operator->, because they may only be defined as non-static member functions, never as non-members. Functions named operator new, operator new[], operator delete, or operator delete[], plus user-defined literal functions whose names start like operator "", follow entirely different sets of rules.)
And when the class argument matches a real function parameter, instead of the "implicit object parameter" of a non-static member function, the type of reference used in the parameter, if any, controls as usual whether an argument can be an lvalue, rvalue, or either.
class B {
public:
// Both increment operators are valid only on lvalues.
friend B& operator++(B& b) {
// Some internal increment logic.
return b;
}
friend B operator++(B& b, int) {
B temp(b);
++temp;
return temp;
}
};
void test_B() {
++B(); // Error: Tried operator++(B()), can't pass
// rvalue B() to B& parameter
B b;
++b; // Okay: Transformed to operator++(b), b is lvalue
++b++; // Error: Tried operator++(operator++(b,0)), but
// operator++(b,0) is prvalue and can't pass to B& parameter
}
Another way is to add ref-qualifiers to member functions, which were added to the language in the C++11 version as a specific way of controlling whether a member function's implicit object argument must be an lvalue or rvalue:
class C {
public:
C& operator++() & {
// Some internal increment logic.
return *this;
}
C operator++(int) & {
C temp(*this);
++temp;
return temp;
}
};
Notice the & between the parameter list and the start of the body. This restricts the function to only accept an lvalue of type C (or something that implicitly converts to a C& reference) as the implicit object argument, similarly to how a const in the same spot allows the implicit object argument to have type const C. If you wanted a function to require an lvalue but allow that lvalue to optionally be const, the const comes before the ref-qualifier: void f() const &;
void test_C() {
++C(); // Error: Tried C().operator++(), doesn't allow rvalue C()
// as implicit object parameter
C c;
++c; // Okay: Transformed to c.operator++(), c is lvalue
++c++; // Error: Tried c.operator++(0).operator++(), but
// c.operator++(0) is prvalue, not allowed as implicit object
// parameter of operator++().
}
To get operator= to act more like it does for a scalar type, we can't use a non-member function, because the language only allows member operator= declarations, but the ref-qualifier will similarly work. You're even allowed to use the = default; syntax to have the compiler generate the body, even though the function isn't declared in exactly the same way an implicitly-declared assignment function would have been.
class D {
public:
D() = default;
D(const D&) = default;
D(D&&) = default;
D& operator=(const D&) & = default;
D& operator=(D&&) & = default;
};
void test_D() {
D() = D(); // Error: implicit object argument (left-hand side) must
// be an lvalue
}
It … just is. There are a few constraints that apply only to primitive types and not class types (well, you've found the most obvious one!).
It is largely because operators for built-in types are one thing, whereas for classes they are just member functions in disguise and therefore a completely different beast.
Is this confusing? I don't know; maybe.
Is there a really compelling reason for it? I don't know; possibly not. There's a certain inertia with primitive types: why change something that was in C just because you're introducing classes? What is the benefit of permitting this? On the other hand, would it not be overly strict to ban it for classes, whose implementation of operator++ could do something that, as the language designer, you haven't thought of?

initialise object with equal operator

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.

&member to const member

I get some errors. I need pass a member to a const member with operator =.
I don't know what is error, my function declaration is:
template<class I> forceinline
void OverweightValues<I>::init(int t,
SharedArray<int>& elements0,
SharedArray<int>& weights0,
I& i) {
{..}
//both are declared as 'const SharedArray<int> elements, weights;'
//but elements0 and weights0 not is const
elements = elements0;
weights = weights0;
When I compile get the error:
In line(16): no operator "=" matches these operands operand types are: const SharedArray<int> = SharedArray<int>
In line(17): no operator "=" matches these operands operand types are: const SharedArray<int> = SharedArray<int>
How can I fix this?
const members can only be set during creation. You'll need to use the constructor initializer list rather than two-phase construction. (Even the constructor body cannot assign them)
As #Ben Voigt mentioned, you can only initialize a const in the constructor initialization list. This is because, by the time the control reaches the constructor body all the class members are already created by the compiler using the default constructor.
Hence, member initialization within the constructor body will actually invoke the assignment constructor which cannot be done for constants, references or objects for which default constructor is not available.

Template assignment operator doesn't replace the default assignment operator

In C++ Templates The Complete Guide in section 5.3 Member Templates it's written:
Note that a template assignment operator doesn't replace the default
assignment operator. For assignments of stacks of the same type, the
default assignment operator is still called.
Is this correct, because when I ran below code:
#include<iostream>
using namespace std;
template<typename T>
class Pair
{
public:
T pair1,pair2;
Pair(T i,T j):pair1(i),pair2(j){}
template<typename T1>Pair<T>& operator=(Pair<T1>&);
};
template<typename T>
template<typename T1>
Pair<T>& Pair<T>::operator=(Pair<T1>& temp)
{
this->pair1 =temp.pair1*10;//At this point
this->pair2=temp.pair2;
return *this;
}
int main()
{
Pair<int>P1(10,20);
Pair<int>P2(1,2);
P2=P1;
cout<<P2.pair1<<' '<<P2.pair2<<endl;
return 1;
}
I got answer 100 20.
It didn't give the default assignment answer.
Is that a typing mistake in C++ Templates the Complete Guide?
C++ Templates: The Complete Guide By David Vandevoorde, Nicolai M.
Josuttis
Publisher : Addison Wesley
Pub Date : November 12, 2002 Pages : 552
The copy assignment operator is indeed implicitly declared and considered by overload resolution.
A user-declared copy assignment operator X::operator= is a
non-static non-template member function of class X [..]. 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 non-static 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.
Otherwise, [..]
As you can see the implicitly-declared copy assignment operator for Pair<int> has one parameter of type Pair<int> const& - note the const in particular! Overload resolution favours non-const references over const ones if both can be bound to the argument, [over.ics.rank]/3:
Two implicit conversion sequences of the same form are
indistinguishable conversion sequences unless one of the following
rules applies:
—
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence
S2 if
[..]
S1 and S2 are reference bindings (8.5.3), and the types to which the
references refer are the same type except for top-level cv-qualifiers,
and the type to which the reference initialized by S2 refers is more
cv-qualified than the type to which the reference initialized by S1
refers.
The specialization of the template lacks a const in the reference parameter, thus it's a better match and is selected.
The default assignment operator accepts the argument as a const reference: http://en.cppreference.com/w/cpp/language/as_operator.
You have defined a version without const, and your version is better in the context of overload resolution (no conversion required).
Try with the following change:
int main()
{
Pair<int>P1(10,20);
Pair<int>P2(1,2);
const Pair<int>& x = P1;
P2=x;
cout<<P2.pair1<<' '<<P2.pair2<<endl;
return 1;
}
to see the expected result.

Is a conversion constructor called for this overloaded operator? (C++)

Given that this is the only == function in the class,
bool myClass::operator == ( const myClass & rhs )
{
if ( var1 == rhs.var1 )
return true;
else
return false;
}
What does the following comparison assume?
myClass mc1;
anotherClass ac1;
if ( mc1 == ac1 )
My instinct was to say that it assumes that ac1 will be converted to type myClass, but how can that happen? operator== has a parameter of type myClass & rhs, so how could myClass's conversion constructor be called when ac1 is passed to the function?
Thanks!
The parameter is of type const myClass & so a conversion call is called, a temporary is constructed and a const-reference to such a temporary is passed to the function. Upon return, the temporary is destroyed.
Since there's no perfect overload for that operator the converting constructor is called (it counts as an implicit conversion), and the temporary created in this way is passed to your operator==. After the call the temporary is destroyed.
Notice that this wouldn't happen if operator== accepted a non-const reference, because temporaries can be bound only to const references.
The compiler takes the left hand side and tries to find the right operator for it.
So it will take the == operator, and try to fit the right hand side to a myClass by casting.
If it can't find implicit casting to myClass it will return a compilation error.
The compiler given mc1 == ac1 will search for the best match for the operator==(A,B) using the normal overload resolution rules for any function.
Assuming it finds your bool myClass::operator==(const myClass&) function the unambiguously best match (this may not be the case. for example it may find an operator declared bool operator==(const myClass&, const anotherClass&) which is superior instead), it will then bind and make the necessary parameter conversions as it would for any function call.
To make the conversion from an lvale of type anotherClass to a const myClass& (assuming anotherClass does not inherit from myClass, in which case no conversion would be needed) it will then search for a single (unambiguously best) converting constructor or conversion operator to turn the parameter into a myClass temporary and then execute the operator== call with that.
It the parameter of a function is a non-const reference than it will not consider performing such temporary conversion. The reason is that a non-const reference usually indicates that the function will perform some sideeffect on that parameter, which would be discarded when the temporary is destroyed leaving the original object uneffected - therefore it would most likely be a logic error accidently discarding this sideeffect than intentionally doing so - so the language designers disallowed it.
Your assumption is correct. The compiler will put it the conversion constructor call first, then call your == method with the converted object as the rhs argument.
There are two other possible answers to your riddle, neither involving conversation constructors.
There exists a free function, bool operator==(const myClass&, const &anotherClass);, or
anotherClass is publicly derived from myClass.