Trouble with Inheritance of Operator= in C++

Trouble with inheritance of operator= in C++

If you do not declare copy-assignment operator in a class, the compiler will declare one for you implicitly. The implicitly declared copy-assignment operator will hide any inherited assignment operators (read about "name hiding" in C++), meaning that any inherited assignment operators will become "invisible" to the unqualified name lookup process (which is what happens when you do b = c), unless you take specific steps to "unhide" them.

In your case, class B has no explicitly declared copy-assignment operator. Which mean that the compiler will declare

B& B::operator =(const B&)

implicitly. It will hide the operator inherited from A. The line

b = c;

does not compile, because, the only candidate here is the above implicitly declared B::operator = (the compiler told you about that already); all other candidates are hidden. And since c is not convertible to B&, the above assignment does not compile.

If you want your code to compile, you can use using-declaration to unhide the inherited A::operator = by adding

using A::operator =;

to the definition of class B. The code will now compile, although it won't be a good style. You have to keep in mind that in this case the b = c assignment will invoke A::operator =, which assigns only the A portions of the objects involved. (But apparently that is your intent.)

Alternatively, in cases like this you can always work around name hiding by using a qualified version of the name

b.A::operator =(c);

operator= and functions that are not inherited in C++?

The assignment operator is technically inherited; however, it is always hidden by an explicitly or implicitly defined assignment operator for the derived class (see comments below).

(13.5.3 Assignment) An assignment operator shall be implemented by a
non-static member function with exactly one parameter. Because a copy
assignment operator operator= is implicitly declared for a a class if
not declared by the user, a base class assignment operator is always
hidden by the copy assignment operator of the derived class.

You can implement a dummy assignment operator which simply forwards the call to the base class operator=, like this:

// Derived class
template<typename T, unsigned int N> class Derived : public Base<Derived, T, N>
{
public:
template<typename T0, class = typename std::enable_if<std::is_convertible<T0, T>::value>::type>
inline Derived& operator=(const T0& rhs)
{
return Base<Derived, T, N>::operator=(rhs);
}
};

Problem with operator inheritance and cpp core guidelines c.128

I suspect that the C26456 warning in this case is a bug, see also https://developercommunityapi.westus.cloudapp.azure.com/content/problem/617702/c26456-false-positive-with-operator-in-derived-cla.html and https://developercommunity.visualstudio.com/content/problem/228085/c-core-check-false-positive-c26434.html.

The referenced core guideline clause C.128 applies only to virtual member functions, but operator= is not virtual in your base class and neither does it have the same signature as in the derived class, so there is no reason for it to apply.


Make sure that you really want the destructor declaration in SimpleState. You have virtual functions in the base class State, which seems to indicate that you want to use State polymorphically and that objects might be destroyed through State pointers, rather than SimpleState pointers. In that case State needs to have a virtual destructor declared, not SimpleState.

If you declare the virtual destructor in State, then you won't need to declare any destructor in SimpleState, which will inherit the virtual destructor from State. Then SimpleState can follow the rule-of-zero and wont need any of the copy/move assignment operators and copy/move constructors declared, which is the preferred way.

Assignment operator inheritance

You don't have a default

Derived& operator=(const Base& a);

in your Derived class.

A default assignment operator, however, is created:

Derived& operator=(const Derived& a);

and this calls the assignment operator from Base. So it's not a matter of inheriting the assignment operator, but calling it via the default generated operator in your derived class.

C++ Trouble with operator code inheritage: am I require to copy same code for all derived classes?

Your question is confusing, and I'm feeling rushed here, but I will say this.

Typically operators are declared outside a class as non-member functions and use member functions to accomplish their goal. This is so that both arguments to the operator can participate equally in overload resolution.

In this case it looks like you're trying to create some sort of parse tree based on C++ operators. In this case, code like this might be what you're looking for:

const B operator +(const B &a, const B &b)
{
return B(a, b);
}

Note that this will work even if something derived from B is either operand. And if I'm right about what you're doing, you will likely have a special node type for two things added to eachother, and operator + can return that.


Now that I see your clarified version, here is a different answer.

First, I question your use of operator + in this way. operator overloading often leads to surprises for people using your class, and unless your use of operator + behaves a lot like people expect operator + to behave in general it will cause more problems that it solves.

But your actual question seems to revolve around creating copies of objects of unknown type without lots of repetitive code. When I see repetitive code, I tend to think templates. Here is some sample code that's probably a little more complex than you need that uses templates and I think solves your problem.

#include <memory>
#include <iostream>

template <class BaseType, class DerivedType>
class Cloneable {
public:
virtual ~Cloneable() {}

::std::auto_ptr<DerivedType> clone() const {
return ::std::auto_ptr<DerivedType>(static_cast<DerivedType *>(i_clone()));
}

protected:
virtual BaseType *i_clone() const {
return new DerivedType(dynamic_cast<const DerivedType &>(*this));
}
};

class A : public Cloneable<A, A> {
public:
A() {}
A(const A &b) {
const void * const voidb = &b;
const void * const voidme = this;
::std::cerr << "Creating a copy of the A at " << voidb << " and this new copy will reside at " << voidme << "\n";
};
virtual ~A() {
const void * const voidme = this;
::std::cerr << "Destroying the A at " << voidme << "\n";
}
};

template <class Derived>
class B : public A, public Cloneable<A, Derived> {
public:
B() {}
B(const B &b) {
const void * const voidb = &b;
const void * const voidme = this;
::std::cerr << "Creating a copy of the B at " << voidb << " and this new copy will reside at " << voidme << "\n";
};
virtual ~B() {
const void * const voidme = this;
::std::cerr << "Destroying the B at " << voidme << "\n";
}
// Make sure clone can be mentioned in derived classes with no ambiguity
using Cloneable<A, Derived>::clone;

protected:
// Force dominance rules to choose the correct i_clone virtual function.
virtual A *i_clone() const {
return Cloneable<A, Derived>::i_clone();
}
};

class C : public B<C> {
public:
C() {}
C(const C &b) {
const void * const voidb = &b;
const void * const voidme = this;
::std::cerr << "Creating a copy of the C at " << voidb << " and this new copy will reside at " << voidme << "\n";
};
virtual ~C() {
const void * const voidme = this;
::std::cerr << "Destroying the C at " << voidme << "\n";
}
};

class D : public B<D> {
public:
D() {}
D(const D &b) {
const void * const voidb = &b;
const void * const voidme = this;
::std::cerr << "Creating a copy of the D at " << voidb << " and this new copy will reside at " << voidme << "\n";
};
virtual ~D() {
const void * const voidme = this;
::std::cerr << "Destroying the D at " << voidme << "\n";
}
};

int main(int argc, const char *argv[])
{
C c;
D d;
::std::auto_ptr<A> cptr(c.clone());
::std::auto_ptr<A> dptr(d.clone());
cptr = dptr->clone();
return 0;
}

I made the i_clone and clone methods so that each class would end up with a version of clone that returned a pointer to the class' own type. I also have to have the using declaration in template class B to make sure there is no ambiguity in which clone is going to be called in derived classes.

Notice how classes C and D contain no repetitive code relating to creating clones of themselves.

While a didn't know it at the time, this appears to be yet another re-invention of The Curiously Recurring Template idiom as applied to polymorphic copy construction.

Why does an overloaded assignment operator not get inherited?

Class Y contains implicitly-declared assignment operators, which hide the operator declared in the base class. In general, declaring a function in a derived class hides any function with the same name declared in a base class.

If you want to make both available in Y, use a using-declaration:

class Y : public X {
public:
using X::operator=;
};

Ambiguity in case of multiple inheritance and spaceship operator in C++20

gcc is correct here.

When you do:

c.operator<(c);

You are performing name lookup on something literally named operator<. There is only one such function (the one in A) so this succeeds.

But when you do c < c, you're not doing lookup for operator<. You're doing two things:

  1. a specific lookup for c < c which finds operator< candidates (member, non-member, or builtin)
  2. finding all rewritten candidates for c <=> c

Now, the first lookup succeeds and finds the same A::operator< as before. But the second lookup fails - because c <=> c is ambiguous (between the candidates in A and B). And the rule, from [class.member.lookup]/6 is:

The result of the search is the declaration set of S(N,T).
If it is an invalid set, the program is ill-formed.

We have an invalid set as the result of the search, so the program is ill-formed. It's not that we find nothing, it's that the whole lookup fails. Just because in this context we're looking up a rewritten candidate rather than a primary candidate doesn't matter, it's still a failed lookup.


And it's actually good that it fails because if we fix this ambiguous merge set issue in the usual way:

  struct C : A, B {
+ using A::operator<=>;
+ using B::operator<=>;
};

Then our lookup would be ambiguous! Because now our lookup for the rewritten candidates finds two operator<=>s, so we end up with three candidates:

  1. operator<(A const&, A const&)
  2. operator<=>(A const&, A const&)
  3. operator<=>(B const&, B const&)

1 is better than 2 (because a primary candidate is better than a rewritten candidate), but 1 vs 3 is ambiguous (neither is better than the other).

So the fact that the original fails, and this one also fails, is good: it's up to you as the class author to come up with the right thing to do - since it's not obvious what that is.

C++: Using parent class operator function to update inherited variables of a child class object

Cube inherits the operator+ from Box. You don't need to implement it again in Cube.
But when removing the Cube operator+, you'll notice that C3 = C1 + C2; gives an error. C1 + C2 results in a Box-type, and you want to assign it to a Cube type. Her you should ask yourself a question: should C3 be a Cube type? If you add two cubes, will you get another cube? I don't think so. So C3 should be Box-type. Then the code works for me.

p.s.You also don't need the this->[member variable]. You already have access to the private member variables, as you define operator+ as a member function.

Edit: just to add working code

#include <iostream>

class Box
{
private:
double length;
double breadth; // ?? do you mean "width"?
double height;

public:
Box() {}
Box(const double l, const double b, const double h)
: length(l)
, breadth(b)
, height(h)
{}

// I don't know why the members are private? But you need access to check for a cube.
double getLength() const { return length; }
double getBreadth() const { return breadth; }
double getHeight() const { return height; }

double getVolume() const
{
return length * breadth * height;
}

// Overload + operator to add two Box objects.
Box operator+(const Box& b)
{
return Box (
length + b.length,
breadth + b.breadth,
height + b.height);
}
};

class Cube : public Box
{
public:
Cube(const double size = 0)
:Box(size, size, size)
{}

Cube(const Box& b)
: Cube(b.getLength())
{
if ((b.getLength() != b.getBreadth()) || (b.getLength() != b.getHeight()))
throw std::invalid_argument("Box is not a Cube!");
}
};

// Main function for the program
int main() {
Cube C1(5), C2(10), C3;
C3 = (C1 + C2);
std::cout << "Volume of C3 : " << C3.getVolume() << std::endl;
int temp;
std::cin >> temp;
return 0;
}

C++ I have some seryous issues with inheritance when derived and base class have different types of parameters, like shown below:

I see at least 3 issues in the code that will lead to a crash/undefined behavior.

First:

parent1(const parent1 &p1)
{
char temp[10001];
strcpy(temp,p1.name);

if(name != NULL) // name isn't initialized yet,
delete[] name; // these 2 lines shouldn't be here
set_new_name(temp);
slr=p1.slr;
age=p1.age;
}

Second: (these ones are reported by the compiler when warnings are enabled)

child1 &operator=(const child1 &p2)
{
set_id(p2.get_id());
parent1::operator=(p2);
return *this; // this line is missing
}

Third:

child2 &operator=(const child2 &p4)
{
char temp7[11];
strcpy(temp7, p4.job);
if(job != NULL)
delete[] job;
set_new_job(temp7);
parent2::operator=(p4);
return *this; // this line is missing
}

The return statement is not "inherited". Each function that's supposed to return something must do so.

With these changes the code runs:

my name
my name
3
6
3
my name 1
my name 2
some code

(Live demo)

Some additional improvement notes:

An array like char ch[10001] can't really be a function argument in C++. When it's used as an argument it silently decays to char *. So you might as well replace all char ch[10001] with const char* ch (and better yet, std::string), to avoid confusion.

Also, there's no point in allocating a temp array. You can just directly do set_new_name(p1.name):

parent1(const parent1 &p1)
{
set_new_name(p1.name);
slr=p1.slr;
age=p1.age;
}

It would be prudent to invest some time in getting familiar with a Debugger. It's all but impossible to make a working application without debugging it. And enable compiler warnings. With GCC use -Wall -Wextra, with MSVC - /W4.

Here's an example of the code using std::string. Thanks to std::string we can follow the rule of 0:

class parent1 {

protected:
float slr = 0;
int age = 0;
string name;
void set_new_name(string const &ch) { name = ch; }

public:
parent1() {}

parent1(string const &name, float slr, int age)
: slr(slr), age(age), name(name) {}

string const &get_name() const { return name; }
void print1();
};

void parent1::print1() { cout << get_name() << '\n'; }

class child1 : public parent1 {

protected:
int id = 0;
void set_id(int j) { id = j; }

public:
child1() : parent1() {}

child1(string const &name, float sl, int ag, int j)
: parent1(name, sl, ag), id(j) {}

int get_id() const { return id; }
void print2();
};

void child1::print2() { cout << get_id() << '\n'; }

class parent2 {

protected:
string name1;
string name2;

void set_new_name1(string const &ch) { name1 = ch; }
void set_new_name2(string const &ch) { name2 = ch; }

public:
parent2() {}

parent2(string const &name1, string const &name2)
: name1(name1), name2(name2) {}

string const &get_name1() const { return name1; }
string const &get_name2() const { return name2; }
void print3();
};

void parent2::print3() {
cout << get_name1() << '\n';
cout << get_name2() << '\n';
}

class child2 : public parent2 {

protected:
string job;
void set_new_job(string const &ch) { job = ch; }

public:
child2() : parent2() {}

child2(string const &name1, string const &name2, string const &job)
: parent2(name1, name2), job(job) {}

string const &get_job() const { return job; }
void print4();
};

void child2::print4() { cout << get_job() << '\n'; }

And this works equally well.



Related Topics



Leave a reply



Submit