Abstract¶
Compared to closures, C++ classes offer a more structured and flexible approach to encapsulating properties and methods into modular, reusable components. Constructors enable object initialization with varying properties, while access specifiers and member functions provide fine-grained control. Shared behavior and data can be managed through static members, and interactions between classes can be facilitated using non-member functions.
from __init__ import *
!mkdir -p privateMotivation¶
Recall an earlier function that increments a static variable count:
%%cpp
unsigned int increment_count() {
static int count;
return ++count;
}
generate_n(ostream_iterator<int>(cout, " "), 5, increment_count);
generate_n(ostream_iterator<int>(cout, " "), 5, increment_count);1 2 3 4 5 6 7 8 9 10 The above code uses std::generate_n from <algorithm> and ostream_iterator from <iterator> to print the 5 consecutive counts separated by spaces.
%%hermes
Explain in a paragraph or two what does the following code do:
---
generate_n(ostream_iterator<int>(cout, " "), 5, increment_count);Hermes
std::generate_n(first, n, gen) is an <algorithm> utility that calls a unary function gen exactly n times and writes each returned value into the output sequence starting at iterator first. In this call, first is an ostream_iterator<int> built on std::cout with " " as a delimiter, so every value produced is printed to the console followed by a space; n is 5; and the generator is increment_count, which returns ++count from a file-scoped static int count. So one invocation of this line calls increment_count() five times in a row and prints each result — first run gives 1 2 3 4 5.
The interesting part is the state: because count is static, it survives between calls and is never reset, so the second identical line in the cell continues where the first left off — hence the output 1 2 3 4 5 6 7 8 9 10. That hidden mutable state inside a plain function is exactly the pattern the notebook then argues encapsulation replaces: bundling that counter into a class member with controlled access makes the state explicit and safer.
How to make the function thread-safe?
Closure can be used in place of the static variable, making the function thread-safe:
%%cpp
std::function<int()> counter(int count=0) {
return [count]() mutable { // thread-safe as count is captured by copy
return ++count;
};
}
auto counter1=counter();
generate_n(ostream_iterator<int>(cout, " "), 5, counter1); cout << '\n';
generate_n(ostream_iterator<int>(cout, " "), 5, counter1); cout << '\n';1 2 3 4 5
1 2 3 4 5
Solution to Exercise 1
This is because counter1 is passed by copy into generate_n, and the copy has its own count captured by value.
How to modify the count of an existing counter?
One way is to modify the returned lambda expression to take an argument new_count:
%%cpp
/**
* @brief Creates a counter function that maintains internal state using a closure.
*
* The returned lambda increments the counter by default, but allows resetting
* the counter by passing a non-negative value.
*
* @param count Initial value of the counter (default is 0).
* @return std::function<int(int)> A function that takes an integer argument:
* - If new_count >= 0: ___
* - If new_count < 0: ___
*/
std::function<int(int)> counter(int count=0) {
return [count](int new_count) mutable {
return new_count>=0? (swap(count, new_count), new_count): ++count;
};
}
auto counter1=counter();
for (auto i=0; i<5; i++) cout << counter1(-1) << ' '; cout << '\n';
counter1(10);
for (auto i=0; i<5; i++) cout << counter1(-1) << ' '; cout << '\n';
counter1(5);
for (auto i=0; i<5; i++) cout << counter1(-1) << ' '; cout << '\n';
// error: no matching function for call to object of type 'std::function<int (int)>'
// for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
11 12 13 14 15
6 7 8 9 10
Solution to Exercise 2
/**
* @brief Creates a counter function that maintains internal state using a closure.
*
* The returned lambda increments the counter by default, but allows resetting
* the counter by passing a non-negative value.
*
* @param count Initial value of the counter (default is 0).
* @return std::function<int(int)> A function that takes an integer argument:
* - If new_count >= 0: resets the counter to new_count and return the previous count.
* - If new_count < 0: increments the counter and return the current count.
*/
std::function<int(int)> counter(int count=0) {
return [count](int new_count) mutable {
return new_count>=0? (swap(count, new_count), new_count): ++count;
};
}To resolve the issue, we will reimplement counter as a data type:
counteris essentially a constructor that creates different counter objectscounter1andcounter2,each with their own data
countbutsharing the same method of incrementing count, namely,
++count.
Class Definition¶
The following defines Counter as a data type, also known as a class or a structure:
struct Counter { // declaration list of members of the class Counter
int count=0; // a property that keeps an integer count, value-initialized to 0
auto operator()() { // a method that defines the function call operator
return ++count;
}
}; // semi-colon required, unlike a compound statement%%cpp
struct Counter {
int count=0;
auto operator()() { return ++count; }
};
Counter counter1;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
counter1.count = 10;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
counter1.count = 5;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
11 12 13 14 15
6 7 8 9 10
The code declares a counter1 object of type Counter and sets its count using counter1.count = 10. The custom data type Counter is defined with the struct keyword. Its members include:
count: a property, field, or data member, behaves like a variable captured by value in a closure—each instance can hold a different value.operator(): a method or member function, resembles a lambda captured by reference—its definition is shared across instances. Implementing the function call operator makes the object a functor, i.e., a callable object.
We can also separate the definition from the declarations as follows:
%%cpp
struct Counter {
int count=0;
auto operator()();
};%%cpp
auto Counter::operator()() { return ++count; }
Counter counter1;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
Note that members are scoped using the class name to avoid name conflicts. The declaration can be placed in a header file, while the definition can be placed in a source file.
The following is an example, which also declares static members shared by all instances:
A constant static property such as
DEFAULT_STARTcan be initialized inline just like a non-static property.However, a non-constant static property such as
startcannot be initialized inline.A static method such as
reset_startcan be defined inline or separately just like a non-static method. However, it cannot access non-static members such ascount, unlike a static method.
%%writefile private/counter.hpp
#pragma once
struct Counter {
int count=start-1;
int operator()();
// Static members shared by all instances
static const int DEFAULT_START=1; // const initializable in header
static int start; // mutable but NOT initializable in header
// static int start=DEFAULT_START;
// error: ISO C++ forbids in-class initialization of non-const static member 'Counter::start'
static void reset_start(); // static method can also be defined in header
};
// int Counter::start=Counter::DEFAULT_START;
// error: gnu/bin/ld: multiple definition of `Counter::start'Writing private/counter.hpp
%%writefile private/counter.cpp
#include "counter.hpp"
int Counter::start=Counter::DEFAULT_START; // initialization of mutable static member
void Counter::reset_start() { start=DEFAULT_START; } // cannot access non-static members like count
int Counter::operator()() { return ++count; } // return type cannot be auto, to match the declarationWriting private/counter.cpp
%%writefile private/static.cpp
#include "counter.hpp"
#include <iostream>
using std::cout;
int main() {
Counter::start=0; // All instances see start as 0
Counter counter1, counter2;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter2() << ' '; cout << '\n';
Counter::reset_start(); // cannot be called as counter1.reset_start()
Counter counter3, counter4;
for (auto i=0; i<5; i++) cout << counter3() << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter4() << ' '; cout << '\n';
return 0;
}Writing private/static.cpp
!cd private && g++ static.cpp counter.cpp -o static && ./static0 1 2 3 4
0 1 2 3 4
1 2 3 4 5
1 2 3 4 5
Solution to Exercise 3
In this code, Counter uses static members (start, DEFAULT_START, and reset_start) to define shared behavior across all instances. Each instance has its own count initialized from the static property start with the default value given by the static constant property DEFAULT_START. Unlike DEFAULT_START, start is mutable and can be reset to the default by calling reset_start.
%%hermes
Explain in a paragraph or two why static constant but not mutable property can
be initialized inside the class declaration, and whether mutable property can be
initialized inside the same header file containing the class declaration?↻ Session: main (ACP: 655b856c-f54…)
Hermes
A non-const static data member is one shared object for the whole program, so the standard requires exactly one out-of-class definition that provides its storage — placing that definition in the header would cause every translation unit that includes it to emit its own copy of Counter::start, which is precisely the multiple definition of 'Counter::start' link error shown commented out in counter.hpp. That’s why static int start; gets its value only in counter.cpp. A const integral static member (static const int DEFAULT_START=1;) gets special treatment: with an in-class initializer it’s treated like an inline definition, so each TU may hold its own copy — but since it’s const and fixed at compile time, every copy is bit-for-bit identical and can never be modified through one copy and observed differently through another, so nothing observable breaks and (if it isn’t odr-used) no separate definition is needed at all. Constness is what makes per-TU copies safe; mutability would make them diverge after a write through one TU.
On initializing mutable members “in the same header”: for non-static members, yes — int count=start-1; right in the class body is fine (C++11 in-class initializers), because those are per-object copies made at construction, not per-TU globals. For a static mutable member, an in-class initializer was forbidden before C++17 (exactly the “ISO C++ forbids in-class initialization of non-const static member” error in your header), but since C++17 you may write static inline int start = DEFAULT_START; (or an out-of-class inline int Counter::start = ...; in the header) — inline tells the compiler to merge all TUs’ copies into one shared object, which is legal and works on this machine (g++ 15 defaults to C++17). Without inline, the definition stays where it is now: once, in a single .cpp.
How to initialize the count?
Is there anything wrong with the following code?
%%cpp
struct Counter {
int count;
auto operator()() { return ++count; }
};
Counter counter1;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
Unlike static/global variables, int count; may not zero-initialize the member count:
%%writefile private/uninitialized.cpp
#include <iostream>
using std::cout;
struct Counter {
int count;
auto operator()() { return ++count; }
};
int main() {
Counter counter1;
cout << counter1() << '\n';
}Writing private/uninitialized.cpp
!cd private && g++ -o uninitialized uninitialized.cpp && ./uninitialized1
%%hermes
Explain in a table the rules for zero initialization in C++.↻ Session: main (ACP: 655b856c-f54…)
Hermes
| Declaration context | Example | Zero-initialized? | Why |
|---|---|---|---|
| Global / namespace-scope variable, no initializer | int x; at file scope | ✅ Yes | Objects of static storage duration are first zero-initialized before any other initialization; scalars become 0 |
Function-local static variable | static int x; inside a function | ✅ Yes | Same rule — static storage duration, implicitly value-initialized |
| Static data member defined out-of-class without initializer | int Counter::start; in the .cpp (if you left off the initializer) | ✅ Yes | It’s a namespace-scope non-class object with no initializer |
| Automatic-storage scalar, no initializer | int x; inside a function (your demo’s member case) | ❌ No — indeterminate garbage | Default-initialized with nothing to do; reading before assignment is UB |
| Scalar explicitly brace-initialized anywhere | int x{};, int x = {}; | ✅ Yes, anywhere | Braces force value-initialization, which for non-class types is zero-initialization — works even on automatic storage |
| Heap allocation without parens vs with parens | new int vs new int() | ❌ vs ✅ | new T = default-initialization (garbage); new T() = value-initialization (zero) |
| Class type, value-initialized, no user-provided constructor | file-scope Counter c;, or local Counter c{}; where Counter has no written ctor | ✅ Yes — scalar members like count become 0 | Value-init of such a class type falls back to zeroing each member |
| Class type, default-initialized on automatic storage | local Counter c; (your demo) | ❌ No for members | The implicit default ctor just runs; an uninitialized scalar member stays indeterminate — which is why the printed first value was lucky stack memory that happened to be 0, not guaranteed behavior |
| Class type with a user-provided default ctor | local or brace-initialized Counter c{}; whose ctor you wrote yourself | ❌ Unless your ctor initializes them | The user-provided ctor replaces the zero-init step entirely — you’re responsible for every member |
The practical takeaway from your demo above: any member declared plain (int count;) needs either an in-class initializer (int count=0;, as earlier cells do), a constructor that sets it, or braces at each construction site — nothing else guarantees it starts at 0.
Solution to Exercise 4
Yes because a variable is zero-initialized in the following situation:
As part of value
-initialization sequence for non-class types and for members of value-initialized class types that have no constructors, including value initialization of elements of aggregates for which no initializers are provided.
Constructor¶
To initialize count to a possibly non-zero value, like the call to auto counter2=counter(10);, we need to define a constructor:
%%cpp
struct Counter {
int count;
Counter(int count) {
this->count = count;
}
auto operator()() { return ++count; }
};
generate_n(ostream_iterator<int>(cout, " "), 5, Counter(0));
Counter counter2(10);
for (auto i=0; i<5; i++) cout << counter2() << ' '; cout << '\n';1 2 3 4 5 11 12 13 14 15
Counter(int count) defines a constructor that takes an int argument and assigns it to the member count. Unlike ordinary functions, constructors omit a return type and a return statement—the constructed object is returned implicitly.
Since the parameter count shadows the member count, this->count is used to explicitly reference the member count:
thispointer is used inside a method to point to the implicit object parameter, i.e., the current object.this->countis a member access operator that translates to(*this).count.
A simpler way to initialize count is to use a member initializer list : count{count} or : count(count) before the body of the constructor:
%%cpp
struct Counter {
int count;
Counter(int count) : count{count} {}
auto operator()() { return ++count; }
};
Counter counter2(10); // direct initialization
for (auto i=0; i<5; i++) cout << counter2() << ' '; cout << '\n';
Counter counter3=20; // copy initialization
for (auto i=0; i<5; i++) cout << counter3() << ' '; cout << '\n';11 12 13 14 15
21 22 23 24 25
We can also provide a comma separated list of non-static data members to the member initializer list. The data members will be initialized in the order.
%%cpp
struct Foo {
int a, b, c;
Foo() : a(0), b(a+1), c(b+2) {
cout << " a: " << a << " b: " << b << " c: " << c << '\n';
}
};
Foo(); a: 0 b: 1 c: 3
The compiler recognizes that the first count in the initializer list : count{count} refers to the data member that is to be initialized, eliminating the need to qualify it with this. This method of initialization is not only more efficient; it is also indispensable when initializing base-class subobjects in derived classes.
The copy initialization Counter counter3=20; may appear confusing as it converts an int to a Counter implicitly using the constructor Counter(int count).
How to make a constructor explicit?
We can simply mark the constructor as explicit:
%%cpp
struct Counter {
int count;
explicit Counter(int count) : count{count} {}
auto operator()() { return ++count; }
};
Counter counter2(10); // direct initialization
for (auto i=0; i<5; i++) cout << counter2() << ' '; cout << '\n';
// Counter counter3=20; // error: no viable conversion from 'int' to 'Counter'11 12 13 14 15
Why does the default initialization fail after declaring the constructor Counter(int count)?
Counter counter1; // default initialization failsSolution to Exercise 5
It fails because the class is missing a default constructor. It worked previously because, when there is no constructor declared, the compiler automatically adds a default constructor.
Modify the following code to force the compiler to create the default constructor using the default keyword in the construction declaration or definition:
class-name() = default;
class-name::class-name() = default;%%cpp
struct Counter {
int count=0;
/*
### BEGIN SOLUTION
*/
Counter () = default;
/*
### END SOLUTION
*/
explicit Counter(int count) : count{count} {}
auto operator()() { return ++count; }
};
Counter counter1, counter2(10);
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter2() << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
11 12 13 14 15
6 7 8 9 10
A even simpler fix is to make the parameter count in the constructor an optional parameter with a default value, which effectively defines the default constructor:
%%cpp
struct Counter {
int count;
explicit Counter(int count=0) : count{count} {}
auto operator()() { return ++count; }
};
Counter counter1, counter2(10);
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter2() << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
11 12 13 14 15
6 7 8 9 10
Counter now plays the same role as the function counter defined at the beginning.
The compiler also implicitly declare the following if they are not user-defined:
%%cpp
Counter counter1;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
Counter counter2(counter1); // implicitly declared copy constructor
for (auto i=0; i<5; i++) cout << counter2() << ' '; cout << '\n';
auto counter3=Counter(10); // implicitly declared move constructor
for (auto i=0; i<5; i++) cout << counter3() << ' '; cout << '\n';
counter2=counter3; // implicitly declared copy assignment
for (auto i=0; i<5; i++) cout << counter2() << ' '; cout << '\n';
counter1=Counter(10); // implicitly declared move assignment
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
counter1.~Counter(); // implicitly declared destructor
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
11 12 13 14 15
16 17 18 19 20
Member Access Specification¶
It is confusing that the count can go negative:
%%cpp
struct Counter {
int count;
explicit Counter(int count=0) : count{count} {}
auto operator()() { return ++count; }
};
Counter counter1(-10);
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';-9 -8 -7 -6 -5
How to prevent users from setting the count to a negative number?
One ways is to define a method set_count to validate the new count before setting it:
%%cpp
struct Counter {
int count;
Counter(int count=0) : count{count} {}
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
};
Counter counter1;
counter1.set_count(-10);
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
The count must be non-negative.
The method void set_count(int count) assigns the parameter to the member count only if it is non‑negative.
How to set the count to a negative number despite having the method set_count?
Solution to Exercise 7
Initialize the count as
Counter counter1(-10);Set the count directly as
counter1.count = -10;
One ways is to define a method set_count to validate the new count before setting it:
struct Counter {
public: // optional as public is the default label for struct
Counter() = default;
explicit Counter(int count) { set_count(count); } // use `set_count` to initialize `count`
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
private: // restrict direct access to members and friends
int count=0; // a common default value for default/invalid initialization
};%%cpp
struct Counter {
Counter() = default;
explicit Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
private:
int count=0;
};
Counter counter1(-10);
counter1.set_count(-10);
// counter1.count=-10; // error: 'count' is a private member of 'Counter'
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
The count must be non-negative.
The count must be non-negative.
By listing
countafter the private access specifierprivate : member-declarationscountcan be accessed only by class members or friends.explicit Counter(int count) { set_count(count); }initializes thecountonly if it is valid. We cannot use member initializer list: count{set_count(count)}sinceset_countdoes not return anint. If the parametercountis invalid, the membercountremains0due to the initialization in the member declarationint count=0;.The default constructor is declared as
Counter() = default;instead ofCounter(int count=0)to reuse the same default value specified in the member declaration.
C++ introduced class which is similar to struct but uses the private access specifier as the default:
class Counter {
private: // optional as private is the default label for class
int count=0;
public: // allow access anywhere
Counter() = default;
explicit Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
};%%cpp
class Counter {
int count=0;
public:
Counter() = default;
explicit Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
};
Counter counter1(-10);
counter1.set_count(-10);
// counter1.count=-10; // error: 'count' is a private member of 'Counter'
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
The count must be non-negative.
The count must be non-negative.
The members list after the public access specifier can be accessed anywhere.
Consider overloading the increment operators as follows:
%%cpp
class Counter {
int count=0;
public:
Counter() = default;
explicit Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
int operator++(int) { return count++; }
int& operator++() { return ++count; }
};
Counter counter1;
++counter1=10;
// error: expression is not assignable
// counter1++=10;
for (auto i=0; i<5; i++) cout << ++counter1 << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1++ << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';11 12 13 14 15
15 16 17 18 19
21 22 23 24 25
The
intparameter in the postfix versionint operator++(int)is a dummy used to differentiate between prefix and postfix versions of the operators.[1]Following the pattern for the built-in operators, the prefix version
int& operator++()returns a reference and the postfix versionint operator++(int)returns a value. Instead of returning theCounter, it is more convenient to return thecount, which can be easily printed and manipulated.
The code fails to ensure the count is non-negative. Why?
Hint
Counter counter1;
++counter1=-10;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';Solution to Exercise 8
Because ++counter1 returns the private member count by reference, so user can rewrite the count to a negative value, bypassing the check in set_counter.
A solution is as follows:
class Counter {
...
public:
...
Counter& operator++() { ++count; return *this; }
Counter& operator=(int count) { set_count(count); return *this; }
};The prefix increment returns
Counter&instead ofint&to enforce the member access specification defined inCounter.The assignment operator is overloaded to reuse
set_countto set the count.
%%cpp
class Counter {
int count=0;
public:
Counter() = default;
explicit Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
int operator++(int) { return count++; }
Counter& operator++() { ++count; return *this; }
Counter& operator=(int count) { set_count(count); return *this; }
};
Counter counter1(10);
++counter1=-10;
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';12 13 14 15 16
The count must be non-negative.
Implements the prefix and postfix decrement operators for Counter properly to avoid negative count.
%%cpp
class Counter {
int count=0;
public:
Counter() = default;
Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
int operator++(int) { return count++; }
Counter& operator++() { ++count; return *this; }
/*
### BEGIN SOLUTION
*/
int operator--(int) { set_count(count-1); return count; }
Counter& operator--() { set_count(count-1); return *this; }
/*
### END SOLUTION
*/
Counter& operator=(int count) { set_count(count); return *this; }
};
Counter counter1;
counter1--; // invalid (count==0)
--counter1=1; // decrement is invalid but assignment is valid (count==1)
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
counter1--; // valid (count==5)
--counter1=-10; // decrement is valid (count==4) but assignment is invalid
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';2 3 4 5 6
5 6 7 8 9
The count must be non-negative.
The count must be non-negative.
The count must be non-negative.
Non-Member Method¶
Unfortunately, the following does not work since the operator << does not handle Counter type:
Counter counter1;
for (auto i=0; i<5; i++) cout << ++counter1 << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1++ << ' '; cout << '\n';One solution is to overload the << operator:
ostream& operator<<(ostream& os, const Counter &c) {
return os << c.count; // error: 'count' is a private member of 'Counter'
}but this will fail as count is a private member of Counter, unless we declare it as a friend to Counter:
class Counter {
...
public:
...
friend ostream& operator<<(ostream& os, const Counter &c) {
return os << c.count;
}
};%%cpp
class Counter {
int count=0;
public:
Counter() = default;
Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
int operator++(int) { return count++; }
Counter& operator++() { ++count; return *this; }
Counter& operator=(int count) { set_count(count); return *this; }
friend ostream& operator<<(ostream& os, const Counter &c) {
return os << c.count;
}
};
Counter counter1;
for (auto i=0; i<5; i++) cout << ++counter1 << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1++ << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';
operator<<(cout, counter1);
// counter1.operator<<(cout, counter1); // error: no member named 'operator<<' in 'Counter'1 2 3 4 5
5 6 7 8 9
11 12 13 14 15
15Note that operator<< is a non-member function of Counter. To separate the friend declaration from its definition:
%%cpp
class Counter {
int count=0;
public:
Counter() = default;
Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
Counter& operator++() { ++count; return *this; }
int operator++(int) { return count++; }
Counter& operator=(int count) { set_count(count); return *this; }
friend ostream& operator<<(ostream& os, const Counter &c);
};%%cpp
ostream& operator<<(ostream& os, const Counter &c) {
return os << c.count;
}
Counter counter1;
for (auto i=0; i<5; i++) cout << ++counter1 << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1++ << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
5 6 7 8 9
11 12 13 14 15
In particular, the function signature for the definition is
ostream& operator<<(ostream& os, const Counter &c) { ... }but neither
ostream& Counter::operator<<(ostream& os, const Counter &c) { ... }since it is not a member of
Counter; norfriend ostream& operator<<(ostream& os, const Counter &c) { ... }since the friends of a class must be declared by the class.
Why not define operator<< as a member function of Counter?
operator<< as a member function of Counter?This is not possible because C++ resolves operators based on the left-hand type, which is std::ostream instead of Counter for std::cout << counter1.
A simpler alternative is to define a conversion operator int():
class Counter {
...
public:
...
operator int() const { return count; }
};Without marking the converter int() as explicit, it is considered in cout << counter1++; to implicitly convert counter1 of type Counter to int, which can be handled by << without implementing a new overload for Counter.
%%cpp
class Counter {
int count=0;
public:
Counter() = default;
Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
int operator++(int) { return count++; }
Counter& operator++() { ++count; return *this; }
Counter& operator=(int count) { set_count(count); return *this; }
operator int() const { return count; }
// operator double() const { return count; } // error: use of overloaded operator '<<' is ambiguous
};
Counter counter1;
for (auto i=0; i<5; i++) cout << ++counter1 << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1++ << ' '; cout << '\n';
for (auto i=0; i<5; i++) cout << counter1() << ' '; cout << '\n';1 2 3 4 5
5 6 7 8 9
11 12 13 14 15
Implement the addition of a Counter with another Counter or an int,
returning a new Counter whose count is the sum of the integer values of the operands.
%%cpp
class Counter {
int count=0;
public:
Counter() = default;
explicit Counter(int count) { set_count(count); }
auto operator()() { return ++count; }
void set_count(int count) {
if (count>=0)
this->count = count;
else cerr << "The count must be non-negative.\n";
}
Counter& operator++() { ++count; return *this; }
int operator++(int) { return count++; }
Counter& operator=(int count) { set_count(count); return *this; }
operator int() const { return count; }
/*
### BEGIN SOLUTION
*/
friend auto operator+(const Counter &lhs, const Counter &rhs) {
return Counter(lhs.count+rhs.count);
}
friend auto operator+(const Counter &lhs, int rhs) {
return Counter(lhs.count+rhs);
}
friend auto operator+(int lhs, const Counter& rhs) {
return Counter(lhs+rhs.count);
}
/* // The following alternative solution does not provide a clean interface
auto operator+(const Counter &other) const {
return Counter(count+other.count);
}
auto operator+(int other) const {
return Counter(count+other);
}
friend auto operator+(int lhs, const Counter& rhs) {
return rhs+lhs;
}
*/
/*
### END SOLUTION
*/
};
Counter counter1, counter2(10), counter3=1+counter1+counter2+2;
for (auto i=0; i<5; i++) cout << counter3() << ' '; cout << '\n';
// 14 15 16 17 1814 15 16 17 18
The parameter is
0by default, but can be changed by calling the operator explicitly ascounter1.operator++(2).