본문으로 건너뛰기

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.