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 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... */
}
};
I am trying to implement a move/copy assignment operators and constructors in a base class for the derived classes using CRTP.
template <typename Derived>
class base {
public:
Derived& operator= (const Derived& other) {
// Copy the base properties:
this->foo_ = other.foo_;
// ...
// Continue as the derived class demands it:
this->derived().copy(other);
return this->derived();
}
// etc. for copy/move assignment/construction...
private:
// To act as the derived class:
Derived& derived () { return *static_cast<Derived*>(this); }
const Derived& derived () const { return *static_cast<const Derived*>(this); }
protected:
// Some base properties:
int foo_;
// ...
};
class derived: public base<derived> {
friend base<derived>;
public:
// Inheriting the constructors and assignment operators:
using base<derived>::base;
using base<derived>::operator=;
private:
void copy (const derived& other) {
// Copy all the needed derived properties:
this->bar_ = other.bar_;
// ...
}
// Some derived properties:
int bar_;
// ...
};
// To test it:
int main () {
derived d, t;
d = t;
}
Compiler gives me an error, saying that derived& derived::operator=(const derived&) cannot be overwritten with derived& base<derived>::operator=(const derived&). My theory is, that somehow derived::operator= gets defined implicitly and then by introducing the base<derived>::operator= by the using declaration I'm trying to redefine it again maybe? This looks suspiciously similar to errors that come up when accidentally defining a method twice.
I compiled this with GCC and the full log is:
test.cpp: In function 'int main()':
test.cpp:25:7: error: 'constexpr derived& derived::operator=(const derived&)' cannot be overloaded
class derived: public base<derived> {
^~~~~~~
test.cpp:4:14: error: with 'Derived& base<Derived>::operator=(const Derived&) [with Derived = derived]'
Derived& operator= (const Derived& other) {
^~~~~~~~
Is this even possible to accomplish, or do I have to define the operators/constructors in the derived class and then delegate their functionality to the base class inside the definition?
Update
OK, maybe after looking at this with a clearer mind, it seems overly complicated. I could just do the following:
Derived& base<Derived>::operator= (const base& other) {
this->foo_ = other.foo_;
return this->self();
}
So the returned type is correct for every derived class and the copy is performed from the base class - only the base properties are copied, which is all I need by default. If I need more, then it's specific to each derived class:
// Adding this to the base class - for any derived class to act as the base one:
template <Derived>
base<Derived>& base<Derived>::base () { *return static_cast<base<Derived>*>(this); }
derived& derived::operator= (const derived& other) {
this->base() = other.base();
this->bar_ = other.bar_;
}
But still, it's an interesting excercise and the question regarding the compiler error remains unanswered.
You can’t usefully declare a “derived operator=” with the usual signature in a base class because, even with a using-declaration, it is always hidden by the implicitly-declared copy assignment operator. (You could use some other signature for one or both of them, but then overload resolution is likely to be …interesting.)
Meanwhile, you’ve found a GCC bug in that it incorrectly concludes that the two operators conflict rather than one hiding the other.
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=.
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.
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=;