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 -@@ void bar (double) override; // @@- Non-Compliant -@@ }; 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 +@@ using B::bar; void bar (double) override; // @@+ Compliant +@@ }; 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 -@@ return sizeof (A); // @@- Non-Compliant: evaluates sizeof (struct A) -@@ }
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 -@@ 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 -@@ }; bool operator || (A const &, A const &); // @@- Non-Compliant -@@ A operator , (A const &, A const &); // @@- Non-Compliant -@@
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 -@@ };
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 +@@ const A operator - (A const &, A const &); // @@- Non-Compliant -@@ A & operator | (A const &, A const &); // @@- Non-Compliant -@@ bool operator == (A const &, A const &); // @@+ Compliant +@@ int32_t operator < (A const &, A const &); // @@- Non-Compliant -@@
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 -@@ bool operator == (A const & other); // @@+ Compliant +@@ }; A operator + (int32_t lhs, A const & rhs); // @@+ Compliant +@@ A operator + (A const & lhs, int32_t rhs); // @@+ Compliant +@@ std::ostream & operator << (std::ostream & o, A const & a); // @@+ Compliant +@@
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 +@@ { return m_x[ a ]; } int32_t operator [] (int32_t a) const // @@+ Compliant: const version +@@ { 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 +@@ { 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 +@@ 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 +@@ { A result (*this); this->operator ++ (); return result; } A& operator -- (); // pre-decrement A operator -- (int) // @@- Non-Compliant: post-decrement -@@ { A result (*this); --m_i; return result; } public: int32_t m_i; };
References
- HIC++ v3.3 - 3.1.9