C++ inheritance of operator = [duplicate] - c++

Until a test I've just made, I believed that only Constructors were not inherited in C++. But apparently, the assignment operator= is not too...
What is the reason of that ?
Is there any workaround to inherit the assignment operator ?
Is it also the case for operator+=, operator-=, ... ?
Are all other functions (apart from constructors/operator=) inherited ?
In fact, I encountered this problem as I was doing some CRTP :
template<class Crtp> class Base
{
inline Crtp& operator=(const Base<Crtp>& rhs) {/*SOMETHING*/; return static_cast<Crtp&>(*this);}
};
class Derived1 : public Base<Derived1>
{
};
class Derived2 : public Base<Derived2>
{
};
Is there any solution to get that working ?
EDIT : OK, I have isolated the problem. Why the following isn't working ? How to solve the problem ?
#include <iostream>
#include <type_traits>
// Base class
template<template<typename, unsigned int> class CRTP, typename T, unsigned int N> class Base
{
// Cast to base
public:
inline Base<CRTP, T, N>& operator()()
{
return *this;
}
// Operator =
public:
template<typename T0, class = typename std::enable_if<std::is_convertible<T0, T>::value>::type>
inline CRTP<T, N>& operator=(const T0& rhs)
{
for (unsigned int i = 0; i < N; ++i) {
_data[i] = rhs;
}
return static_cast<CRTP<T, N>&>(*this);
}
// Data members
protected:
T _data[N];
};
// Derived class
template<typename T, unsigned int N> class Derived : public Base<Derived, T, N>
{
};
// Main
int main()
{
Derived<double, 3> x;
x() = 3; // <- This is OK
x = 3; // <- error: no match for 'operator=' in ' x=3 '
return 0;
}

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

The assignment operator is inherited, sort of, but... In any given
class, if you do not provide a copy assignment operator, the compiler
generates one for you. That means that your derived classes effectively
have an assignment operator:
Derived& operator=( Derived const& );
And the usual hiding rules apply; this hides all of the base class
assignment operators. (If the base class had an assignment operator
with this signature, the derived class would inherit it normally.)

Your assignment operator is technically inherited, but then it's hidden by the default copy assignment operator in the derived class. This default copy assignment then tries to call the base class's copy assignment which doesn't exist since you hid it with your own assignment.
The sanest way to resolve this is to not use operator overloading in non-obvious ways (= not meaning copy assignment for example). In this case, don't use operator=: Call it something like assign or set and then it will inherit and not be hidden by the child copy assignment.
These operators are inherited and there are no compiler versions so they will never be automatically hidden like operator=.
It really is only constructors that aren't inherited, and I can't think of any other compiler-generated functions that could hide something from the parent as in operator=.

Related

c++20 default comparison operator and empty base class

c++20 default comparison operator is a very convenient feature. But I find it less useful if the class has an empty base class.
The default operator<=> performs lexicographical comparison by
successively comparing the base (left-to-right depth-first) and then
non-static member (in declaration order) subobjects of T to compute
<=>, recursively expanding array members (in order of increasing
subscript), and stopping early when a not-equal result is found
According to the standard, the SComparable won't have an operator<=> if base doesn't have an operator<=>. In my opinion it's pointless to define comparison operators for empty classes. So the default comparison operators won't work for classes with an empty base class.
struct base {};
struct SComparable: base {
int m_n;
auto operator<=>(SComparable const&) const& = default; // default deleted, clang gives a warning
};
struct SNotComparable: base {
int m_n;
};
If we are desperate to use default comparison operators and therefore define comparison operators for the empty base class base. The other derived class SNotComparable wrongly becomes comparable because of its empty base class base.
struct base {
auto operator<=>(base const&) const& = default;
};
struct SComparable: base {
int m_n;
auto operator<=>(SComparable const&) const& = default;
};
struct SNotComparable: base { // SNotComparable is wrongly comparable!
int m_n;
};
So what is the recommended solution for using default comparison operators for classes with an empty base class?
Edit: Some answers recommend to add default comparison operator in the empty base class and explicitly delete comparison operator in non-comparable derived classes.
If we add default comparison operator to a very commonly used empty base class, suddenly all its non-comparable derived classes are all comparable (always return std::strong_ordering::equal). We have to find all these derived non-comparable classes and explicitly delete their comparison operators. If we missed some class and later want to make it comparable but forget to customize its comparison operator (we all make mistakes), we get a wrong result instead of a compile error from not having default comparison operator in the empty base as before. Then why do I use default comparison operator in the first place? I would like to save some efforts instead of introducing more.
struct base {
auto operator<=>(base const&) const& = default;
};
struct SComparable: base {
int m_n;
auto operator<=>(SComparable const&) const& = default;
};
struct SNotComparable1: base {
int m_n;
auto operator<=>(SNotComparable1 const&) const& = delete;
};
struct SNotComparableN: base {
int m_n;
// oops, forget to delete the comparison operator!
// if later we want to make this class comparable but forget to customize comparison operator, we get a wrong result instead of a non-comparable compile error.
};
In my opinion it's pointless to define comparison operators for empty classes.
Well, it's clearly not pointless. If what you want to do is default your type's comparisons, that necessarily implies comparing all of your type's subobjects, including the base class subobjects, which requires them to be comparable - even if they're empty.
What you need to do is provide them - just conditionally. The simplest way of doing so is probably to provide a different empty base class:
struct base { /* ... */ };
struct comparable_base : base {
friend constexpr auto operator==(comparable_base, comparable_base)
-> bool
{
return true;
}
friend constexpr auto operator<=>(comparable_base, comparable_base)
-> std::strong_ordering
{
return std::strong_ordering::equal;
}
};
And then inherit from comparable_base when you want to have comparisons, and base when you don't. That is:
struct SComparable: comparable_base {
int m_n;
auto operator<=>(SComparable const&) const& = default;
};
struct SNotComparable: base {
int m_n;
};
I'm using hidden friend comparisons there just to be able to take the type by value - since it's empty. Could just as easily be a member function too.
what is the recommended solution for using default comparison operators for classes with an empty base class?
The solution is to add the default comparator to the base class and then do what you do in SComparable if you want the added member(s) of SComparable to be included in the comparison - just as with a base class with members.
If you don't want them to be included in the comparison, don't add a default comparator, like you do in SNotComparable - and the base class comparator will be used - again, just like in a base class with members.
If you don't want the base class behavior in SNotComparable and you don't want SNotComparable to be comparable, then delete the comparator, just like you would if the base class had members:
auto operator<=>(SNotComparable const&) const& = delete;
I'd like to make a small modification based on #Barry's answer. We could have a generic mix-in class comparable<EmptyBase> that provides comparable operators for any empty base. If we want to use default comparison operators for a class derived from empty base class(es), we can simple derive such class from comparable<base> instead of base. It also works for chained empty bases comparable<base1<base2>>.
struct base { /* ... */ };
template<typename EmptyBase>
struct comparable: EmptyBase {
static_assert(std::is_empty<EmptyBase>::value);
template<typename T> requires std::same_as<comparable>
friend constexpr auto operator==(T const&, T const&)
-> bool
{
return true;
}
template<typename T> requires std::same_as<comparable>
friend constexpr auto operator<=>(T const&, T const&)
-> std::strong_ordering
{
return std::strong_ordering::equal;
}
};
struct SComparableDefault: comparable<base> {
int m_n;
auto operator<=>(SComparableDefault const&) const& = default;
};
struct SNotComparable: base {
int m_n;
};
struct SComparableNotDefault: base {
int m_n;
constexpr bool operator==(SComparableNotDefault const& rhs) const& {
/* user defined... */
}
constexpr auto operator<=>(SComparableNotDefault const& rhs) const& {
/* user defined... */
}
};

Template operator overload of template base class

I'm having trouble calling a template assignment operator overload method, of a template base class. This is what I have so far:
BaseClass.h:
template<typename T>
class BaseClass
{
template<typename U>
BaseClass<T>& operator=(const BaseClass<U>& o)
{
return *this;
};
};
ChildClassInt.h:
#include "BaseClass.h"
class ChildClassInt : public BaseClass<int>
{
};
ChildClassFloat.h:
#include "BaseClass.h"
class ChildClassFloat : public BaseClass<float>
{
};
main.cpp:
#include "ChildClassInt.h"
#include "ChildClassFloat.h"
int main()
{
const ChildClassFloat floatClass;
ChildClassInt intClass;
return 0;
}
How do I call the operator=() method of intClass and pass it floatClass?
intClass always has an implicitly defined assignment operator, which always hides assignment operators from base classes. To explicitly call the base class assignment operator requires explicit qualification:
intClass.BaseClass<int>::operator=(floatClass);
(Note: Your code has two errors. First, BaseClass<int>::operator= is private. Second, it should return *this, not this.)
However, you probably should not do this. In general, assigning to a subobject can result in a broken complete object, since the base class assignment operator isn't aware of the derived class's invariants. If you really need to assign a ChildClassFloat to a ChildClassInt, you should define an assignment operator:
ChildClassInt& ChildClassInt::operator=(const ChildClassFloat& c) {
BaseClass<int>::operator=(c);
return *this;
}
Then the user can just do:
intClass = floatClass;
Since this assignment operator is part of the implementation of ChildClassInt, it knows how to perform the assignment in a way that preserves the invariants of ChildClassInt and will be updated if necessary when the implementation changes. The user, on the other hand, cannot be expected to know whether directly performing partial assignment to a base class will work, and, even if it does, the user code can be broken at any time by a change to the class implementation.

Overloading baseclass assignment operator in subclass class leads to ambiguous assignment error

I have this base class (details removed)
template<class T>
class GPtr
{
public:
typedef T BaseType;
GPtr& operator=(const BaseType& rhs)
{
m_p = rhs.get();
return *this;
}
private:
BaseType m_p;
};
Then a sub-class specialises the template and adds another assignment option:
class GDrawablePtr : public GPtr<XYZ>
{
public:
GDrawablePtr& operator=(const RootType& rhs)
{
GPtr::operator =(convert<BaseType::element_type>(rhs));
return *this;
}
/* -- only compiles if this is uncommented
GDrawablePtr& operator=(const BaseType& rhs)
{
GPtr::operator =(rhs);
return *this;
}
*/
};
With that code commented out, I get compilation errors about ambiguous assignment when assigning instances. If I uncomment it, then even though it doesn't appear to do anything new, compilation is successful.
Is there a way to avoid re-defining the original base assignment operator, and what is the reason for this behaviour?
It's known as hiding: declaring a function in a derived class makes any function in the base class with the same name inaccessible. You can use a using-declaration to make the base class versions available too:
// In GDrawablePtr
using GPtr::operator=;

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

Until a test I've just made, I believed that only Constructors were not inherited in C++. But apparently, the assignment operator= is not too...
What is the reason of that ?
Is there any workaround to inherit the assignment operator ?
Is it also the case for operator+=, operator-=, ... ?
Are all other functions (apart from constructors/operator=) inherited ?
In fact, I encountered this problem as I was doing some CRTP :
template<class Crtp> class Base
{
inline Crtp& operator=(const Base<Crtp>& rhs) {/*SOMETHING*/; return static_cast<Crtp&>(*this);}
};
class Derived1 : public Base<Derived1>
{
};
class Derived2 : public Base<Derived2>
{
};
Is there any solution to get that working ?
EDIT : OK, I have isolated the problem. Why the following isn't working ? How to solve the problem ?
#include <iostream>
#include <type_traits>
// Base class
template<template<typename, unsigned int> class CRTP, typename T, unsigned int N> class Base
{
// Cast to base
public:
inline Base<CRTP, T, N>& operator()()
{
return *this;
}
// Operator =
public:
template<typename T0, class = typename std::enable_if<std::is_convertible<T0, T>::value>::type>
inline CRTP<T, N>& operator=(const T0& rhs)
{
for (unsigned int i = 0; i < N; ++i) {
_data[i] = rhs;
}
return static_cast<CRTP<T, N>&>(*this);
}
// Data members
protected:
T _data[N];
};
// Derived class
template<typename T, unsigned int N> class Derived : public Base<Derived, T, N>
{
};
// Main
int main()
{
Derived<double, 3> x;
x() = 3; // <- This is OK
x = 3; // <- error: no match for 'operator=' in ' x=3 '
return 0;
}
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);
}
};
The assignment operator is inherited, sort of, but... In any given
class, if you do not provide a copy assignment operator, the compiler
generates one for you. That means that your derived classes effectively
have an assignment operator:
Derived& operator=( Derived const& );
And the usual hiding rules apply; this hides all of the base class
assignment operators. (If the base class had an assignment operator
with this signature, the derived class would inherit it normally.)
Your assignment operator is technically inherited, but then it's hidden by the default copy assignment operator in the derived class. This default copy assignment then tries to call the base class's copy assignment which doesn't exist since you hid it with your own assignment.
The sanest way to resolve this is to not use operator overloading in non-obvious ways (= not meaning copy assignment for example). In this case, don't use operator=: Call it something like assign or set and then it will inherit and not be hidden by the child copy assignment.
These operators are inherited and there are no compiler versions so they will never be automatically hidden like operator=.
It really is only constructors that aren't inherited, and I can't think of any other compiler-generated functions that could hide something from the parent as in operator=.

Why assignment operators of parent class are not accessible from derived class objects

Example:
class C
{
public:
void operator =(int i) {}
};
class SubC : public C
{
};
The following gives compilation error:
SubC subC;
subC = 0;
"no match for 'operator=' in 'subC = 0'"
Some sources state that it is because assignment operators are not inherited. But isn't it simply because default constructed copy-assignment of SubC overshadows them?
The copy assignment operator is automatically generated in the derived class. This causes the base class's assignment operator to be hidden due to the regular name hiding rules of C++. You can unhide the name in the base class through the "using" directive. For example:
class C
{
public:
void operator =(int i) {}
};
class SubC : public C
{
public:
using C::operator=;
};
A copy assignment operator for a base class does not have the signature required for a copy assignment operator for a derived class. It is inherited by the derived class, but does not constitute a copy assignment operator in it. So even though assignment operators are inherited, just like other member functions, it does not provide copy assignment.
I haven't done it, but according to The Man Himself (Stroustrup) it's a feature of C++11 to do it with constructors, but it's been in since C++98 to do it with other methods.
This is DIRECTLY lifted from the link:
People sometimes are confused about the fact that ordinary scope rules
apply to class members. In particular, a member of a base class is not
in the same scope as a member of a derived class:
struct B {
void f(double);
};
struct D : B {
void f(int);
};
B b; b.f(4.5); // fine
D d; d.f(4.5); // surprise: calls f(int) with argument 4
In C++98, we can "lift" a set of overloaded functions from a base
class into a derived class:
struct B {
void f(double);
};
struct D : B {
using B::f; // bring all f()s from B into scope
void f(int); // add a new f()
};
B b; b.f(4.5); // fine
D d; d.f(4.5); // fine: calls D::f(double) which is B::f(double)
So there ya go. You can probably "take it if you want it" even before C++11, though I haven't tried it myself.
Except copy-assignment operator, other overloaded operator can be inherited.
I agree the opinion that default constructed copy-assignment of SubC overshadows overloaded assignment operator of C.
If SubC don't provide a copy-assignment operator, Compiler would synthese a copy-assignment operation,
as follow:
class SubC : public C
{
public:
SubC & operator=( const SubC & other );
}
then the 'SubC & operator=( const SubC & other )' overshadows assignment operator of C,
results in compile error.
If
SubC other;
SubC subC;
subC = other;
then, this case, compile ok.