Operator Overloading and Conversions
|
This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com. This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Member vs. Non-Member Operators
Binary operators can be a member (the left operand is implicit this) or a free function; a free function is
required whenever the left operand isn’t your type (as with std::ostream <<), and is the conventional choice
even when a member *would work, so both operand types get the same implicit conversions applied:
class Vector2D {
public:
Vector2D(double x, double y) : x_(x), y_(y) {}
Vector2D operator+(const Vector2D& other) const { // member: works for v1 + v2
return Vector2D(x_ + other.x_, y_ + other.y_);
}
double x() const { return x_; }
double y() const { return y_; }
private:
double x_, y_;
};
Vector2D operator*(double scalar, const Vector2D& v) { // free function: needed for 2.0 * v
return Vector2D(scalar * v.x(), scalar * v.y()); // (v * 2.0 could be a member; scalar * v cannot,
} // since double isn't Vector2D)
operator<⇒ and Defaulted Comparisons
Covered briefly in Operators and Expressions;
hand-writing <⇒ (rather than = default) lets a type return a specific ordering category:
#include <compare>
class Fraction {
public:
Fraction(int num, int den) : num_(num), den_(den) {
if (den_ < 0) { num_ = -num_; den_ = -den_; } // normalize the sign into the numerator -- otherwise
} // cross-multiplying by a negative flips the ordering
std::strong_ordering operator<=>(const Fraction& other) const {
long long lhs = static_cast<long long>(num_) * other.den_;
long long rhs = static_cast<long long>(other.num_) * den_;
return lhs <=> rhs; // cross-multiply to compare without floating-point division
}
bool operator==(const Fraction& other) const {
return (*this <=> other) == std::strong_ordering::equal;
}
private:
int num_, den_;
};
std::strong_ordering (a total order, substitutable equality), std::weak_ordering (a total order, equal
values need not be substitutable), and std::partial_ordering (comparisons can be "unordered", e.g. NaN) are
the three categories <⇒ may return.
operator[] (Multidimensional in C++23)
C++23 allows operator[] to take multiple arguments (previously limited to exactly one), so a matrix-like
type no longer needs operator() or chained single-index calls as a workaround:
#include <vector>
class Matrix {
public:
Matrix(std::size_t rows, std::size_t cols)
: rows_(rows), cols_(cols), data_(rows * cols, 0.0) {}
double& operator[](std::size_t row, std::size_t col) { // C++23: two-argument operator[]
return data_[row * cols_ + col];
}
double operator[](std::size_t row, std::size_t col) const {
return data_[row * cols_ + col];
}
std::size_t rows() const { return rows_; }
std::size_t cols() const { return cols_; }
private:
std::size_t rows_, cols_;
std::vector<double> data_;
};
operator()
Overloading the call operator makes an object callable like a function — a function object or functor,
usable anywhere a callable is expected (algorithms, std::function, sorting comparators):
class Adder {
public:
explicit Adder(int amount) : amount_(amount) {}
int operator()(int value) const { return value + amount_; }
private:
int amount_;
};
// Adder addFive(5); addFive(10) == 15;
Conversion Operators and explicit
A conversion operator lets a user-defined type convert to another type; explicit on it (C++11) blocks
implicit use the same way it does on constructors:
class Fraction2 {
public:
Fraction2(int num, int den) : num_(num), den_(den) {}
explicit operator double() const { // explicit: must write static_cast<double>(f), not "double d = f;"
return static_cast<double>(num_) / den_;
}
explicit operator bool() const { return num_ != 0; } // common idiom: explicit operator bool for
// "is this valid/non-empty" checks (see std::optional)
private:
int num_, den_;
};
Stream Operators
Overloading <</>> for std::ostream/std::istream is what makes a type printable/readable with the usual
stream syntax — always as a free function, since the left operand is the stream, not your type:
#include <iostream>
class Point {
public:
Point(int x, int y) : x_(x), y_(y) {}
int x() const { return x_; }
int y() const { return y_; }
private:
int x_, y_;
};
std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << '(' << p.x() << ", " << p.y() << ')';
}
std::istream& operator>>(std::istream& is, Point& p) {
int x, y;
if (is >> x >> y) { p = Point(x, y); }
return is;
}