본문으로 건너뛰기

Overloaded Operations and Conversions

Operator Overloading: Defines the meaning of an existing C++ operator for operands of class type.

Operator overloading lets class objects behave naturally with expressions such as:

item1 + item2
stream << item
value[index]
iterator++

An overloaded operator should preserve the usual meaning and expectations of the corresponding built-in operator.


14.1 Basic Concepts​

An overloaded operator is a function whose name consists of:

operator
+
operator symbol

Example:

Sales_data operator+(
const Sales_data&,
const Sales_data&);

Operator Parameters​

A unary operator has one operand.

!object

A binary operator has two operands.

lhs + rhs

If the operator is a nonmember function, the operands correspond directly to its parameters.

operator+(lhs, rhs);

If the operator is a member function, the left-hand operand is bound to the implicit this pointer.

lhs.operator+=(rhs);

Therefore a member binary operator has only one explicit parameter.


Operators Require a Class-Type Operand​

An overloaded operator must either:

  • be a member of a class, or
  • have at least one parameter of class type

This is invalid:

// Error:
int operator+(int, int);

The meaning of operators for built-in types cannot be changed.


Existing Operators Only​

C++ allows existing operators to be overloaded.

A new operator symbol cannot be invented.

// Invalid idea:
// operator**

The following operators cannot be overloaded:

::
.*
.
?:

Precedence and Associativity Do Not Change​

An overloaded operator keeps the precedence and associativity of the corresponding built-in operator.

x == y + z

is still interpreted as:

x == (y + z)

Operator overloading changes the operation performed, not the grammar of an expression.


Calling an Operator Function Directly​

An overloaded operator can usually be invoked either with operator syntax:

a + b

or as a function:

operator+(a, b)

For a member operator:

a += b;

is equivalent to:

a.operator+=(b);

Use Operator Overloading Carefully​

An overloaded operator should have a meaning that users naturally associate with that operator.

Good examples include:

+ addition or concatenation
== equality
[] indexed access
<< output
```

Avoid giving an operator an unrelated meaning.

For example, `operator+` should not perform subtraction.

---

### Member or Nonmember?

Some operators must be members:

~~~text
=
[]
()
->

Compound-assignment operators such as += usually should be members.

Operators that modify an object or are closely associated with it, such as:

++
--
*
```

are also usually members.

Symmetric operators are usually nonmembers:

~~~text
+
-
==
!=
<
>
```

This allows conversions to be considered for either operand.

---

### Why Symmetric Operators Are Usually Nonmembers

Suppose a class supports conversion from another type.

A nonmember operator can allow either operand to be converted.

Conceptually:

~~~cpp
string_value + "text";
"text" + string_value;

If operator+ were a member, the left-hand operand would have to already be an object of the class type.


14.2 Input and Output Operators​

Classes that support IO often overload:

<< output
>> input

These operators should follow the conventions of the standard IO library.


14.2.1 Overloading the Output Operator <<​

A typical output operator has the form:

std::ostream&
operator<<(
std::ostream& output,
const Sales_data& item);

The first parameter is a nonconst reference because writing changes the stream state.

The second parameter is usually a reference to const because printing normally does not modify the object.

The operator returns the stream so output operations can be chained.


Output Operator Example​

std::ostream&
operator<<(
std::ostream& output,
const Sales_data& item)
{
output
<< item.isbn()
<< ' '
<< item.units_sold
<< ' '
<< item.revenue
<< ' '
<< item.avg_price();

return output;
}

Usage:

std::cout
<< item
<< '\n';

Output Operators Should Use Minimal Formatting​

An output operator should normally print the object's data without imposing extra formatting.

In particular, it should usually not print a newline.

output << item;

The caller should decide whether to add:

'\n'

or other descriptive text.


IO Operators Are Nonmembers​

To support:

std::cout << item;

the left-hand operand is an ostream.

Therefore the operator cannot be a member of Sales_data.

IO operators are ordinarily nonmember functions.

If they need access to private data, they are often declared as friends.


14.2.2 Overloading the Input Operator >>​

A typical input operator has the form:

std::istream&
operator>>(
std::istream& input,
Sales_data& item);

The object parameter is nonconst because input changes the object.

The function returns the input stream so operations can be chained.


Input Operator Example​

std::istream&
operator>>(
std::istream& input,
Sales_data& item)
{
double price = 0.0;

input
>> item.bookNo
>> item.units_sold
>> price;

if (input)
{
item.revenue =
item.units_sold
* price;
}
else
{
item =
Sales_data();
}

return input;
}

If the input fails, the object is reset to a valid state.


Input Error Handling​

An input operator should decide how to handle invalid input.

If the data was read successfully but fails additional validation, the operator can set:

input.setstate(
std::ios::failbit
);

Use failbit for input-format or validation failure.

Do not set eofbit or badbit unless those conditions actually apply.


14.3 Arithmetic and Relational Operators​

Arithmetic and relational operators are usually:

  • nonmember functions
  • given references to const operands
  • written so they do not modify their operands

Arithmetic Operators Return a New Value​

An arithmetic operator usually creates and returns a new object.

Sales_data
operator+(
const Sales_data& lhs,
const Sales_data& rhs)
{
Sales_data result = lhs;

result += rhs;

return result;
}

The original operands remain unchanged.


Arithmetic and Compound Assignment​

If a class defines both:

+
+=
```

it is usually best to implement `+` using `+=`.

Conceptually:

~~~text
copy lhs
↓
apply += rhs
↓
return result

This keeps the two operations consistent and avoids duplicating logic.


14.3.1 Equality Operators​

Equality Operator: Determines whether two objects represent equivalent values.

Example:

bool operator==(
const Sales_data& lhs,
const Sales_data& rhs)
{
return
lhs.isbn()
== rhs.isbn()
&&
lhs.units_sold
== rhs.units_sold
&&
lhs.revenue
== rhs.revenue;
}

Inequality Operator​

If a class defines ==, it should usually also define !=.

bool operator!=(
const Sales_data& lhs,
const Sales_data& rhs)
{
return !(lhs == rhs);
}

One equality operator should perform the actual comparison.

The other should delegate to it.


Equality Design Rules​

A good equality operation should:

  • compare the data that determines the object's value
  • behave consistently
  • be transitive

If:

a == b
b == c

then:

a == c

should also be true.


14.3.2 Relational Operators​

A class should define relational operators only when there is a meaningful ordering.

Example of a natural ordering:

bool operator<(
const Record& lhs,
const Record& rhs)
{
return
lhs.id < rhs.id;
}

Ordering and Equality Must Agree​

If a class defines both:

==
<
```

their meanings should be consistent.

Normally, if neither object is less than the other, they should be considered equivalent under the class's ordering model.

---

### Do Not Invent an Arbitrary Ordering

Some types have no single natural ordering.

For example, a sales record might be ordered by:

- ISBN
- revenue
- number of units sold

If no one ordering is clearly the meaning of `<`, it is better not to overload `<`.

---

## 14.4 Assignment Operators

A class can overload assignment for operand types other than the class itself.

All assignment operators must be member functions.

---

### Assignment from an Initializer List

Example:

~~~cpp
class StrVec
{
public:
StrVec&
operator=(
std::initializer_list<
std::string
>);
};

Implementation:

StrVec&
StrVec::operator=(
std::initializer_list<
std::string
> values)
{
auto data =
alloc_n_copy(
values.begin(),
values.end()
);

free();

elements =
data.first;

first_free =
cap =
data.second;

return *this;
}

Assignment operators ordinarily return a reference to the left-hand operand.


Compound-Assignment Operators​

Compound-assignment operators are not required to be members, but they usually should be.

Example:

Sales_data&
Sales_data::operator+=(
const Sales_data& rhs)
{
units_sold +=
rhs.units_sold;

revenue +=
rhs.revenue;

return *this;
}

Returning *this allows normal assignment behavior.


14.5 Subscript Operator​

Subscript Operator: Provides indexed access through operator[].

It must be a member function.


Nonconst Subscript​

std::string&
operator[](
std::size_t index)
{
return elements[index];
}

Returning a reference allows assignment:

values[0] =
"zero";

Const Subscript​

Container-like classes should usually also define a const version.

const std::string&
operator[](
std::size_t index) const
{
return elements[index];
}

For a const object, the returned element cannot be modified.


Const and Nonconst Pair​

Typical design:

class StrVec
{
public:
std::string&
operator[](
std::size_t index)
{
return elements[index];
}

const std::string&
operator[](
std::size_t index) const
{
return elements[index];
}

private:
std::string* elements;
};

14.6 Increment and Decrement Operators​

Iterator-like classes often define:

++
--
```

Both prefix and postfix forms may be overloaded.

---

### Prefix Increment

Prefix increment normally modifies the object and returns a reference to the modified object.

~~~cpp
StrBlobPtr&
StrBlobPtr::operator++()
{
check(
curr,
"increment past end"
);

++curr;

return *this;
}

Usage:

++iter;

Postfix Increment​

Postfix increment takes an unused int parameter to distinguish it from prefix increment.

StrBlobPtr
StrBlobPtr::operator++(
int)
{
StrBlobPtr old =
*this;

++*this;

return old;
}

Usage:

iter++;

The object is advanced, but the returned value represents its old state.


Prefix vs. Postfix​

++iter
modify
return modified object

iter++
save old object
modify
return old value

Prefix usually returns a reference.

Postfix usually returns a value.


Explicit Calls​

Prefix:

iter.operator++();

Postfix:

iter.operator++(0);

The integer argument exists only to select the postfix overload.


14.7 Member Access Operators​

Iterator-like and smart-pointer-like classes often overload:

*
->

Dereference Operator​

std::string&
StrBlobPtr::operator*() const
{
auto pointer =
check(
curr,
"dereference past end"
);

return
(*pointer)[curr];
}

The operator returns the object represented by the iterator-like class.


Arrow Operator​

std::string*
StrBlobPtr::operator->() const
{
return
&this->operator*();
}

Usage:

iter->size();

Conceptually:

(*iter).size();

Rules for operator->​

operator-> must be a member function.

Its result must be:

  • a pointer to a class object, or
  • an object that itself defines operator->

C++ repeatedly applies overloaded operator-> until it obtains a built-in pointer through which member access can occur.


14.8 Function-Call Operator​

Function-Call Operator: operator() lets an object be used with function-call syntax.

It must be a member function.


Basic Function Object​

struct AbsInt
{
int operator()(
int value) const
{
return
value < 0
? -value
: value;
}
};

Usage:

AbsInt absolute;

int value =
absolute(-42);

This invokes:

absolute.operator()(-42);

Function Object​

Function Object: An object of a class that defines operator().

Unlike an ordinary function, a function object can store state in data members.


Function Object with State​

class PrintString
{
public:
PrintString(
std::ostream& output =
std::cout,
char separator = ' ')
: stream(output),
sep(separator)
{
}

void operator()(
const std::string& text) const
{
stream
<< text
<< sep;
}

private:
std::ostream& stream;
char sep;
};

Usage:

PrintString printer;

printer("hello");

Function objects are commonly passed to generic algorithms.


14.8.1 Lambdas Are Function Objects​

A lambda expression creates an unnamed class type whose objects behave like function objects.

Example:

std::size_t minimum = 5;

auto predicate =
[minimum](
const std::string& word)
{
return
word.size()
>= minimum;
};

Conceptually, the compiler creates something similar to:

class SizeComp
{
public:
explicit SizeComp(
std::size_t size)
: minimum(size)
{
}

bool operator()(
const std::string& word) const
{
return
word.size()
>= minimum;
}

private:
std::size_t minimum;
};

Captured values become state stored in the generated function object.


Lambda Captures as Data Members​

For:

[minimum](
const std::string& word)
{
return
word.size()
>= minimum;
}

the captured minimum is conceptually stored as a data member of the generated class.

The function body becomes the generated operator().


14.8.2 Library-Defined Function Objects​

The standard library provides function-object templates in:

#include <functional>

Important arithmetic function objects include:

plus<T>
minus<T>
multiplies<T>
divides<T>
modulus<T>
negate<T>

Relational function objects include:

equal_to<T>
not_equal_to<T>
greater<T>
greater_equal<T>
less<T>
less_equal<T>

Logical function objects include:

logical_and<T>
logical_or<T>
logical_not<T>

Library Function Object Example​

std::plus<int> add;

int result =
add(10, 20);

Result:

30

Function Objects with Algorithms​

To sort in descending order:

std::sort(
values.begin(),
values.end(),
std::greater<int>()
);

The algorithm calls the function object to compare elements.


14.8.3 Callable Objects and function​

Callable Object: Anything that can be invoked using function-call syntax.

Examples include:

function
function pointer
lambda
bind expression
function object

These objects may have different types even when they accept the same arguments and return the same result type.


std::function​

std::function is a library type that can store different callable objects with the same call signature.

Required header:

#include <functional>

Example:

std::function<
int(int, int)
> operation;

The type says that operation can store a callable that:

takes two int arguments
returns int

Storing Different Callables​

Function:

int add(
int lhs,
int rhs)
{
return lhs + rhs;
}

Lambda:

auto multiply =
[](int lhs, int rhs)
{
return lhs * rhs;
};

Store either in std::function:

std::function<
int(int, int)
> operation = add;

operation = multiply;

Callable Map​

Different operations can share one common callable interface.

std::map<
std::string,
std::function<
int(int, int)
>
> operations;

Example entries:

operations["+"] =
[](int a, int b)
{
return a + b;
};

operations["*"] =
[](int a, int b)
{
return a * b;
};

Then:

int result =
operations["+"](
10,
20
);

Overloaded Functions and std::function​

An overloaded function name may be ambiguous because it represents several possible functions.

When necessary, select the desired overload explicitly with:

  • a function pointer of the exact type, or
  • a lambda that calls the intended overload

This gives std::function a single unambiguous callable.


14.9 Overloading, Conversions, and Operators​

Class types can participate in automatic type conversion.

These conversions interact with:

  • constructors
  • conversion operators
  • ordinary overload resolution
  • overloaded operators

Careless combinations can create ambiguous expressions.


14.9.1 Conversion Operators​

Conversion Operator: A special member function that converts an object of a class type to another type.

General form:

operator type() const;

Example:

class SmallInt
{
public:
operator int() const
{
return value;
}

private:
std::size_t value = 0;
};

Usage:

SmallInt object;

int number =
object;

The conversion operator can be invoked implicitly.


Conversion Operator Rules​

A conversion operator:

  • must be a member function
  • has no explicit return type
  • has no parameter list
  • names the target type after operator
  • is usually const

Example:

operator int() const;

Avoid Surprising Conversions​

Conversion operators should be defined only when there is a clear and useful mapping between the class and the target type.

If several different interpretations are equally reasonable, ordinary named member functions are usually clearer.


Explicit Conversion Operators​

C++11 allows conversion operators to be declared explicit.

class SmallInt
{
public:
explicit
operator int() const
{
return value;
}

private:
std::size_t value = 0;
};

Now an ordinary implicit conversion is not allowed.

Use an explicit cast:

int number =
static_cast<int>(
object
);

Explicit Conversion in Conditions​

An explicit conversion operator can still be used implicitly in a condition.

This is especially important for conversion to bool.

class Flag
{
public:
explicit
operator bool() const
{
return valid;
}

private:
bool valid = false;
};

Then:

if (flag)
{
// ...
}

is allowed.


Stream Conversion to bool​

The standard IO types provide an explicit conversion to bool.

Therefore:

while (
std::cin >> value
)
{
// ...
}

works by testing whether the stream remains in a usable state.


14.9.2 Avoiding Ambiguous Conversions​

Multiple possible user-defined conversions can make an expression ambiguous.

Example design problem:

Class A converts to Class B

and

Class B can be constructed from Class A

Now the compiler may have multiple ways to perform the same conceptual conversion.

Avoid defining overlapping conversion paths unless there is a clear need.


Multiple Arithmetic Conversions​

A class that defines several conversions to arithmetic types can cause ambiguous expressions.

Example:

class Number
{
public:
operator int() const;
operator double() const;
};

An expression involving another arithmetic operand may have more than one viable conversion sequence.

Prefer one natural conversion or explicit named operations.


Converting Constructors and Conversion Operators​

Conversions can be introduced by:

converting constructor
conversion operator

Defining both directions between two class types can make overload resolution difficult and surprising.

Design class conversions conservatively.


14.9.3 Function Matching and Overloaded Operators​

An operator expression can have several candidate functions.

For an expression such as:

lhs + rhs

the compiler may consider:

  • member operator+
  • nonmember operator+
  • built-in operator candidates
  • conversions of the operands

Normal overload-resolution rules are then used to choose the best viable candidate.


Member and Nonmember Candidates Compete​

Suppose a type defines both a member and a nonmember form that are equally good matches.

The expression can become ambiguous.

Avoid defining equivalent competing operator overloads.


Conversion Effects on Operator Matching​

User-defined conversions participate in overload resolution.

An operator overload that appears simple can therefore become ambiguous if several class conversions are available.

The safest design is to:

provide only meaningful conversions
avoid duplicate conversion paths
keep operator meanings natural
```

---

## Essential Study Checklist

1. An overloaded operator is a function named with `operator` followed by an operator symbol.
2. At least one operand of an overloaded operator must have class type.
3. Operator overloading cannot change operators for built-in types.
4. New operator symbols cannot be invented.
5. `::`, `.*`, `.`, and `?:` cannot be overloaded.
6. Operator precedence and associativity do not change when an operator is overloaded.
7. `=`, `[]`, `()`, and `->` must be member functions.
8. Symmetric operators such as arithmetic and equality operators are usually nonmembers.
9. Overloaded operators should preserve the natural meaning of the built-in operator.
10. Output `operator<<` normally takes `ostream&` and `const T&`, returns `ostream&`, and uses minimal formatting.
11. Input `operator>>` normally takes `istream&` and `T&`, returns `istream&`, and should leave the object valid on failure.
12. Arithmetic operators usually return a new value and are often implemented using compound assignment.
13. If a class defines `==`, it should usually also define `!=`.
14. Relational operators should be defined only when the type has a clear and consistent ordering.
15. Assignment operators must be members and normally return a reference to the left-hand operand.
16. `operator[]` must be a member and container-like classes usually provide both const and nonconst versions.
17. Prefix increment normally returns the modified object by reference; postfix increment returns the old value and uses an unused `int` parameter.
18. `operator->` must be a member and must ultimately yield a pointer through which member access can occur.
19. `operator()` makes an object callable; such objects are called function objects.
20. Lambdas are implemented conceptually as compiler-generated function-object classes.
21. `<functional>` provides standard arithmetic, relational, and logical function objects.
22. `std::function` can hold different callable types that share one call signature.
23. A conversion operator is a parameterless member function with no explicit return type.
24. `explicit` conversion operators prevent most implicit conversions but may still be used in conditions.
25. Multiple converting constructors, conversion operators, and operator overloads can create ambiguous overload resolution.