Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

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 private

Motivation

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);

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:

  • counter is essentially a constructor that creates different counter objects counter1 and counter2,

  • each with their own data count but

  • sharing 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_START can be initialized inline just like a non-static property.

  • However, a non-constant static property such as start cannot be initialized inline.

  • A static method such as reset_start can be defined inline or separately just like a non-static method. However, it cannot access non-static members such as count, 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 declaration
Writing 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 && ./static
0 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?
Loading...
↻ Session: main (ACP: 655b856c-f54…)

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 && ./uninitialized
1
%%hermes
Explain in a table the rules for zero initialization in C++.
Loading...
↻ Session: main (ACP: 655b856c-f54…)
Solution to Exercise 4

Yes because a variable is zero-initialized in the following situation:

  1. 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.

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 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 
Solution 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.

%%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.

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 count after the private access specifier

    private : member-declarations

    count can be accessed only by class members or friends.

  • explicit Counter(int count) { set_count(count); } initializes the count only if it is valid. We cannot use member initializer list : count{set_count(count)} since set_count does not return an int. If the parameter count is invalid, the member count remains 0 due to the initialization in the member declaration int count=0;.

  • The default constructor is declared as Counter() = default; instead of Counter(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 int parameter in the postfix version int 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 version int operator++(int) returns a value. Instead of returning the Counter, it is more convenient to return the count, which can be easily printed and manipulated.

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 of int& to enforce the member access specification defined in Counter.

  • The assignment operator is overloaded to reuse set_count to 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.
%%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 
15

Note 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; nor

  • friend ostream& operator<<(ostream& os, const Counter &c) { ... }

    since the friends of a class must be declared by the class.

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 
%%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 18
14 15 16 17 18 
Footnotes
  1. The parameter is 0 by default, but can be changed by calling the operator explicitly as counter1.operator++(2).