13. Overloading
13.1 Overload Resolution
13.1.1 Ensure that all overloads of a function are visible from where it is called
When a member function is overridden or overloaded in a derived class, other base class functions of that name will be hidden. A call to a function from the derived class may therefore result in a different function being called than if the same call had taken place from the base class. To avoid this situation, hidden names should be introduced into the derived class through a using declaration.
#include <cstdint> class B { public: void foo (uint32_t); virtual void bar (uint32_t); virtual void bar (double); }; class D : public B { public: void foo (double); // @@- Non-Compliant [email protected]@ void bar (double) override; // @@- Non-Compliant [email protected]@ }; void f1 () { D d; d.foo (0U); // D::foo (double) called B & b (d); b.foo (0U); // B::foo (uint32_t) called d.bar (0U); // D::bar (double) called b.bar (0U); // B::bar (uint32_t) called } class E : public B { public: using B::foo; void foo (double); // @@+ Compliant [email protected]@ using B::bar; void bar (double) override; // @@+ Compliant [email protected]@ }; void f2 () { E d; d.foo (0U); // B::foo (uint32_t) called B & b (d); b.foo (0U); // B::foo (uint32_t) called d.bar (0U); // B::bar (uint32_t) called b.bar (0U); // B::bar (uint32_t) called }
A using declaration for a namespace scope identifier, only brings into the current scope the prior declarations of this identifier, and not any declarations subsequently added to the namespace. This too may lead to unexpected results for calls to overloaded functions.
#include <cstdint> namespace NS { void foo (int32_t); struct A { int32_t a; int32_t b; }; } using NS::foo; using NS::A; namespace NS { void foo (uint32_t); int A; } uint32_t bar (uint32_t u) { foo (u); // @@- Non-Compliant: foo (int32_t) called [email protected]@ return sizeof (A); // @@- Non-Compliant: evaluates sizeof (struct A) [email protected]@ }
References
- HIC++ v3.3 – 3.3.5
- HIC++ v3.3 – 3.3.11
- MISRA C++:2008 – 7-3-5
13.1.2 If a member of a set of callable functions includes a universal reference parameter, ensure that one appears in the same position for all other members
A callable function is one which can be called with the supplied arguments. In the C++ language standard, this is known as the set of viable functions. A template parameter declared T&& has special rules during type deduction depending on the value category of the argument to the function call. Scott Meyers has named this a ‘Universal Reference’.
#include <cstdint> template<typename T> void f1 (T&&t); void f2 () { int32_t i; f1(i); // 't' has type int &, T has type 'int &' f1(0); // 't' has type int &&, T has type 'int' }
As a universal reference will deduce perfectly for any type, overloading them can easily lead to confusion as to which function has been selected.
#include <cstdint> template <typename T> void f1 (T&&t); // #1 // @@- Not Compliant [email protected]@ void f1 (int&&t); // #2 void f2() { int32_t i = 0; f1(i); // Calls #1 f1(+i); // Calls #2 f1(0); // Calls #2 f1(0U); // Calls #1 }
Exception
Standard C++ allows for a member of the viable function set to be deleted. In such cases, should these functions be called then it will result in a compiler error.
For Example:
# include <cstdint> template <typename T> void f (T&&t); void f ( int32_t &) = delete ; // Compliant int main () { int32_t i; f (0); f (i); }
References
- Meyers Effective C++ ’11 (draft TOC) – Avoid overloading on universal references
13.2 Overloaded Operators
13.2.1 Do not overload operators with special semantics
Overloaded operators are just functions, so the order of evaluation of their arguments is unspecified. This is contrary to the special semantics of the following built-in operators:
- && — left to right and potentially evaluated
- || — left to right and potentially evaluated
- , — left to right
Providing user declared versions of these operators may lead to code that has unexpected behavior and is therefore harder to maintain.
class A { public: bool operator && (A const &); // @@- Non-Compliant [email protected]@ }; bool operator || (A const &, A const &); // @@- Non-Compliant [email protected]@ A operator , (A const &, A const &); // @@- Non-Compliant [email protected]@
Additionally, overloading the unary & (address of) operator will result in undefined behavior if the operator is used from a location in the source where the user provided overload is not visible.
class A; A * foo (A & a) { return & a; // a.operator& not visible here } class A { public: A * operator & (); // @@- Non-Compliant: undefined behavior [email protected]@ };
References
- HIC++ v3.3 – 3.5.1
- JSF AV C++ Rev C – 159
- JSF AV C++ Rev C – 168
- MISRA C++:2008 – 5-3-3
- MISRA C++:2008 – 5-2-11
- MISRA C++:2008 – 5-18-1
13.2.2 Ensure that the return type of an overloaded binary operator matches the built-in counterparts
Built-in binary arithmetic and bitwise operators return a pure rvalue (which cannot be modified), this should be mirrored by the overloaded versions of these operators. For this reason the only acceptable return type is a fundamental or an enumerated type or a class type with a reference qualified assignment operator. Built-in equality and relational operators return a boolean value, and so should the overloaded counterparts.
#include <cstdint> class A { public: A & operator=(A const &) &; // ... }; A operator + (A const &, A const &); // @@+ Compliant [email protected]@ const A operator - (A const &, A const &); // @@- Non-Compliant [email protected]@ A & operator | (A const &, A const &); // @@- Non-Compliant [email protected]@ bool operator == (A const &, A const &); // @@+ Compliant [email protected]@ int32_t operator < (A const &, A const &); // @@- Non-Compliant [email protected]@
References
- HIC++ v3.3 - 3.5.3
13.2.3 Declare binary arithmetic and bitwise operators as non-members
Overloaded binary arithmetic and bitwise operators should be non-members to allow for operands of different types, e.g. a fundamental type and a class type, or two unrelated class types.
#include <iostream> class A { public: bool operator * (A const & other); // @@- Non-Compliant [email protected]@ bool operator == (A const & other); // @@+ Compliant [email protected]@ }; A operator + (int32_t lhs, A const & rhs); // @@+ Compliant [email protected]@ A operator + (A const & lhs, int32_t rhs); // @@+ Compliant [email protected]@ std::ostream & operator << (std::ostream & o, A const & a); // @@+ Compliant [email protected]@
References
- HIC++ v3.3 - 3.5.4
13.2.4 When overloading the subscript operator (operator[]) implement both const and non-const versions
A non-const overload of the subscript operator should allow an object to be modified, i.e. should return a reference to member data. The const version is there to allow the operator to be invoked on a const object.
#include <cstdint> class Array { public: Array () { for (int32_t i = 0; i < Max_Size; ++i ) { m_x [i] = i; } } int32_t & operator [] (int32_t a) // @@+ Compliant: non-const version [email protected]@ { return m_x[ a ]; } int32_t operator [] (int32_t a) const // @@+ Compliant: const version [email protected]@ { return m_x[ a ]; } private: static const int32_t Max_Size = 10; int32_t m_x [Max_Size]; }; void foo () { Array a; int32_t i = a [3]; // non-const a [3] = 33; // non-const Array const ca; i = ca [3]; // const ca [3] = 33; // compilation error }
References
HIC++ v3.3 - 3.5.5
13.2.5 Implement a minimal set of operators and use them to implement all other related operators
In order to limit duplication of code and associated maintenance overheads, certain operators can be implemented in terms of other operators.
Binary Arithmetic and Bitwise Operators
Each binary arithmetic or bitwise operator can be implemented in terms of its compound assignment counterpart.
class A { public: A & operator += (A const & other); }; A const operator + (A const & lhs, A const & rhs) // @@+ Compliant [email protected]@ { A result (lhs); result += rhs; return result; }
The additional benefit of this implementation is that by virtue of rule, these operators do not have to access to member data directly, however, they do not need to be declared as friends of the associated class.
Relational and Equality Operators
In principle operator < is sufficient to provide all other relational and equality operators.
#include <utility> class A { public: bool operator < (A const & rhs) const; bool operator == (A const & rhs) const { return !((*this) < rhs) && !(rhs < (*this)); } // @@+ Compliant [email protected]@ bool operator != (A const & rhs) const { return std::rel_ops::operator != (*this, rhs); } bool operator <= (A const & rhs) const { return std::rel_ops::operator <= (*this, rhs); } bool operator > (A const & rhs) const { return std::rel_ops::operator > (*this, rhs); } bool operator >= (A const & rhs) const { return std::rel_ops::operator >= (*this, rhs); } };
However, operator == is not required to be defined as above, as a direct implementation may be more efficient, or when relational operators are not implemented for a particular class.
Increment and Decrement Operators
The post-increment operator should be implemented in terms of pre- increment. Similarly, for the decrement operators.
#include <cstdint> class A { public: A (); A& operator ++ (); // pre-increment A operator ++ (int) // @@+ Compliant: post-increment [email protected]@ { A result (*this); this->operator ++ (); return result; } A& operator -- (); // pre-decrement A operator -- (int) // @@- Non-Compliant: post-decrement [email protected]@ { A result (*this); --m_i; return result; } public: int32_t m_i; };
References
- HIC++ v3.3 - 3.1.9