© Will Briggs 2021
W. BriggsC++20 for Lazy Programmershttps://doi.org/10.1007/978-1-4842-6306-8_27

27. Esoterica (Not So Recommended)

Will Briggs1  
(1)
Lynchburg, VA, USA
 

I rarely use these features. Part of the reason for this chapter is for those rare circumstances in which they are useful. Another is understanding why they’re less popular. And because it’s fun to look ahead, there are sneak previews of two C++20 features that aren’t so useful yet, but should be in C++23: modules and coroutines.

protected sections, protected inheritance

Consider class Phone. Phone has a member numCalls_ which keeps track of all calls made, ever, by any Phone. There’s a function to change it, but it’s private, because we really should only update numCalls_ when making a call ().
class Phone
{
public:
    void call() { /*do some stuff, and then */ incNumCalls(); }
    static int numCalls() { return numCalls_; }
private:
    void incNumCalls   () { ++numCalls_;      }
    inline static int numCalls_ = 0;
};

But now we’ve reached the dawn of human civilization and there are MobilePhones. They make calls differently, using cell towers, but they need to increment that number too. They can’t access Phone::incNumCalls(); it’s private. And we decided for good reason not to make it public. What else can we do?

C++ provides another section: protected (see Example 27-1). The outside world can’t see it (like private), but it’s visible to child classes.
class Phone
{
public:
    void call() { /*do some stuff, and then */ incNumCalls(); }
    static int numCalls() { return numCalls_; }
protected:
    void incNumCalls   () { ++numCalls_;      }
private:
    inline static int numCalls_ = 0;
};
Example 27-1

The Phone class, ready to share a family secret with its child classes

Now MobilePhone will be able to access incNumCalls():
class MobilePhone : public Phone
{
public:
    void call() { /* do stuff w cell towers, and */ incNumCalls(); }
};
Should we use public or private inheritance? Let’s say when you do a mobile call, you need some extra security. So in MobilePhone I’ll ditch the old call and add a new function secureCall:
class MobilePhone : public /*?*/ Phone
{
public:
    void secureCall()
    {
        makeSecure ();
        /* do cell tower stuff */
        incNumCalls();
    }
    void makeSecure (); // however that's done
};
With public inheritance (Figure 27-1), the inherited members are as public in the child class as they were in the parent. That’s bad for MobilePhone: it lets the outside world use the insecure, inherited call function. Maybe private inheritance, as in Figure 27-2, would be better? Looks like it.
../images/477913_2_En_27_Chapter/477913_2_En_27_Fig1_HTML.png
Figure 27-1

Public inheritance with a protected section

../images/477913_2_En_27_Chapter/477913_2_En_27_Fig2_HTML.png
Figure 27-2

Private inheritance with a protected section

Now I’ll add a subclass of MobilePhone: a SatellitePhone. It does its calling differently:
class SatellitePhone : public MobilePhone
{
public:
    void secureCall()
    {
        makeSecure ();
        /* do satellite stuff */
        incNumCalls();
    }
    //  makeSecure is inherited from MobilePhone
};

Problem: SatellitePhone can’t use incNumCalls. Private inheritance put it in MobilePhone’s private section.

We can use protected inheritance , as in Figure 27-3. It’s like public inheritance, except inherited public members become protected.
../images/477913_2_En_27_Chapter/477913_2_En_27_Fig3_HTML.png
Figure 27-3

protected inheritance: a solution to SatellitePhone’s problem

Now, as the program in Example 27-2 can verify, the subclasses are secure against use of the old call function, and all classes access incNumCalls() as needed.
int main ()
{
    Phone P;            P.call();
    MobilePhone MP;    MP.secureCall();
    SatellitePhone SP; SP.secureCall();
    assert (Phone::numCalls() == 3); // If this assertion succeeds,
                                     //    incNumCalls got called //     3 times -- good!
    return 0;
}
Example 27-2

Verifying that no matter what kind of call we made, Phone::numCalls_ got updated

It doesn’t at all matter whether you use private or protected inheritance until you have a grandchild class. Even then it probably won’t matter. I almost never need protected sections or protected inheritance.

friends and why you shouldn’t have any

Consider a program that uses maps. It reads in several Areas, as illustrated by Figure 27-4. Each Area has a name and bounding box (how far the Area extends north, south, west, and east). It then reports which Area is furthest north. Examples 27-3 and 27-4 show source code, with some code omitted for brevity.
../images/477913_2_En_27_Chapter/477913_2_En_27_Fig4_HTML.jpg
Figure 27-4

A four-Area map, with a bounding box shown on one Area

// Class Area
// Each Area is read in as
//   <north bound> <south bound> <west bound> <east bound> <name>
//   as in
//   8 2 1 4 Blovinia
// ...and that's what an Area contains
//       -- from _C++20 for Lazy Programmers_
#ifndef AREA_H
#define AREA_H
#include <string>
#include <iostream>
class Area
{
public:
    static constexpr int NORTH = 0,
                         SOUTH = 1,
                         EAST  = 2,
                         WEST  = 3;
    static constexpr int DIRECTIONS = 4 ; // there are 4 directions
    Area () {}
    Area (const Area& other);
    Area& operator= (const Area& other);
    void read  (std::istream& in );
    void print (std::ostream& out) const { out << name_; }
private:
    double boundingBox_[DIRECTIONS];
        // the northernmost, southernmost, etc., extent of our Area
        // bigger numbers are further north
        // bigger numbers are further east
    std::string name_;
};
inline
bool furtherNorthThan (const Area& a, const Area& b)
{
    return a.boundingBox_[Area::NORTH] > b.boundingBox_[Area::NORTH];
}
#endif //AREA_H
Example 27-3

area.h

// Program to read in regions from a file, and tell which
//   is furthest north.
//       -- from _C++20 for Lazy Programmers_
#include <iostream>
#include <fstream>
#include <vector>
#include "area.h"
using namespace std;
int main ()
{
    vector<Area> myAreas;
    ifstream infile("regions.txt");
    if (!infile)
    {
        cerr << "Can't open file regions.txt.\n"; return 1;
    }
    while (infile)                // read in Areas
    {
        Area area; infile >> area;
        if (infile) myAreas.push_back (area);
    }
    // find the northernmost Area
    int northernmostIndex = 0;
    for (unsigned int i = 1; i < myAreas.size(); ++i)
        if (furtherNorthThan (myAreas[i],myAreas[northernmostIndex]))
            northernmostIndex = i;
    // print it
    cout << "The northernmost area is "
         << myAreas [northernmostIndex]
         << endl;
    return 0;
}
Example 27-4

The map program, which identifies the  Area  furthest north

I know I’ve written clear, well-commented code here (and I’m humble too), so I won’t explain further. But when furtherNorthThan tries to access boundingBox_, the compiler complains of a privacy violation. It’s right: boundingBox_ is private.

C++ friends are a fix for this. If a function is so closely associated with a class that it may as well be a member – but it isn’t convenient to make it one – you can give it access to all members, including the private ones, just as though it were. Here’s how: somewhere in class Area (I put it at the top so it’s always the same place), put a friend declaration for the function (Example 27-5).
class Area
{
    // "friend" keyword plus prototype of the trusted function
    friend bool furtherNorthThan (const Area& a, const Area& b);
    ...
Example 27-5

A friend for Area

Now the program should compile fine and report that Morgravia is furthest north.

You can also make a class a friend:
class Area
{
    friend class OtherClassITrust;1
    ...
Or make some other class’s member function a friend:
class Area
{
    friend void OtherClassIPartlyTrust::functionIFullyTrust();
    ...

Is this a good idea?

According to Marshall Cline and the C++ Super-FAQ,2 yes. He argues that a friend function is part of the public interface just as a public member function is. It doesn’t violate security, but is just another part of it.

I see his point, but I can’t think of an example that can’t be done another way. In this example, we could replace bool furtherNorthThan (const Area& a, const Area& b); with bool Area::furtherNorthThan (const Area& b) const; . That’s what we do with operators like <. Why not this too?

I used to make stream I/O operators >> and << friends of the classes they printed/read; now I have them call member functions print and read. Using friend might be easier, but not by much.

If you want it, use it as experts suggest: for things tightly connected to the class in question, so they can be considered part of the class’s interface to the world. I’m betting it won’t be often.

User-defined conversions (cast operators)

Should we add a way to implicitly cast from String to const char* as needed? Makes sense. Many built-in functions expect a char*, and you might prefer myInFile.open (filename); to myInFile.open (filename.c_str()), especially around the 100th time you type it. So we’ll add this operator to String:operator const char* () const { return c_str (); } // called implicitly as needed.

Works fine for that call to myInFile.open. Then we try a simple string comparison:
if (str1 == "END")
    cout << "Looks like we've reached the END.\n";

It no longer compiles – gives complaints about ambiguity or too many overloads.

It’s right. There are now two ways to match the arguments of operator ==: implicitly convert "END" to another String and compare with String’s ==; and implicitly convert str1 to a char* with the cast operator and use char*’s ==.

The solution is to put the word explicit in front of the function (Example 26-7).3
class String
{
public:
    ...
    explicit operator const char* () const { return c_str(); }
        // will cast from String to const char* if explicitly called
    ...
};
Example 27-6

Giving String a user-defined cast operator

Now we can cast, but we have to say we want to cast:
myInputFile.open ((const char*) (filename));           //old-style explicit //   cast -- OK
or
myInputFile.open (static_cast<const char*>(filename)); //modern explicit //   cast -- OK

It works, but did we gain anything over saying filename.c_str()?

I never seem to find a way to use this feature that is both safe and time-saving. Maybe you will.

Exercises
In each exercise, use explicit to avoid ambiguity.
  1. 1.

    Add a cast-to-double operator to the Fraction class. The double version of 1/2, for example, is 0.5 (of course).

     
  2. 2.

    Add a cast-to-double operator to the Point2D class from earlier exercises. The double version of a Point2D is the magnitude: $$ \sqrt{{\mathrm{x}}^2+{\mathrm{y}}^2} $$.

     

Modules

Programmers who aren’t me get concerned about the time it takes to load those increasingly long .h files. There are also issues we’ve skirted: a #define you include in one .h file may interfere with another. We try to avoid that by conventions for naming those defines; we may fail and get horrific error messages.

Modules are a fix. A module can be compiled once, rather than recompiled for every .cpp file that uses it, unlike a .h file;4 that should shorten compile times. It can also specify what its writer wants shared with the world, preventing some name conflicts. (A .h file makes everything visible to its includer, and a .cpp file shows nothing, but a module gets to choose.) And a module can all be in one file if you want – you don’t have to split between .cpp and .h files.

I’m sure it’ll work as planned. But the standard itself isn’t complete: one of the “top priorities” for the upcoming C++23 standard is to put the standard library in modules, which means they haven’t done it yet.5 I’ll wait, and I recommend you do as well.

But of course I can’t let it go at that – so here’s an online extra.

Online Extra: Using Modules Right Now

See github.com/Apress/cpp20-for-lazy-programmers for an up-to-date walk-through of how to use modules as best compilers now support them.

Coroutines

Ordinarily a function, if you call it a second time, starts at the beginning again. A coroutine can pick up right where it left off.

Example 27-6 uses a coroutine to calculate the next factorial. (For a refresher on factorials, see Chapter 18’s section on recursion) It’s the co_yield that makes C++ recognize it as a coroutine.6 The std::experimental::generator <int> return type means “set this up so factorial can generate ints.”
// Program to print several factorials using a coroutine
//      -- from _C++ for Lazy Programmers_
#include <iostream>
#include <experimental/generator>
std::experimental::generator<int> factorial()
{
    int whichOne = 0;                 // start with 0!
    int result = 1;                   // 0! is 1
    while (true)
    {
        co_yield result;
        ++whichOne;                   // go on to next one
        result *= whichOne;           // and calculate next result
    }
}
int main ()
{
    std::cout << "The first 8 factorials:    \n";
    for (int i : factorial())
    {
        static int whichOne = 0;
        std::cout << whichOne << ": " << i << '\n';
        ++whichOne;                  // go on to next
        if (whichOne > 8) break;     // stop at 8
    }
    std::cout << std::endl;
    return 0;
}
Example 27-7

A program using a coroutine in Microsoft Visual Studio. g++ and the C++20 standard aren’t equipped for this yet

To trace its action: the first time it’s called, it sets whichOne – which factorial we’re to return – to 0. The result for 0 is 1. (You might load the source code example in the ch26 folder and trace it in the debugger. That’s what I did.)

It enters the loop. First thing it’s to do is provide the caller main with that result, with co_yield, which means “give result to the caller, and when called again, resume execution here.” So control returns to main, which prints that result.

When main calls it again, it continues from where it stopped: at the co_yield. It goes on and adds 1 to whichOne (changing whichOne to 1), multiplies result by whichOne (getting 1 again), goes to the next iteration of the loop, and co_yields that result.

When called again, it’ll increment whichOne again (getting 2), multiply result by whichOne (getting 2), and co_yield that result.

The next time, whichOne will become 3 and result will be 6. And so on.

main is set up to call this again and again in a range-based for loop, breaking at 8 (gotta stop somewhere).

One advantage of coroutines is efficiency. Each time we call factorial, all it does is an increment, a multiplication, and a return. It’s O(1)! The version in Chapter 18 was O(N). Programmers also report that for some problems, coroutines are more intuitive and easier to write.

The big disadvantage for now is support. As you saw, Visual Studio considers its generator template experimental, and g++ doesn’t have it at all. Both support coroutines – both have co_await, co_result, and co_yield – but generator isn’t standard, and I think it’s best to write code that’ll work on any machine. In g++ you’d have to write it yourself, and it’s not easy. Same for other things you may want to do. My hope – and there’s chatter in the community about this7 – is that C++23 will remedy this problem.

Exercises
  1. 1.

    Adapt Example 27-7 so that factorial returns not just result, but result with whichOne in a structured binding – so main won’t have to keep track of its own whichOne independently. This isn’t really practice with coroutines but with structured bindings, but still worth doing, I think.

     
  2. 2.

    Write another generator function that returns the next prime number, starting with 2, and a version of main that prints the first 100 primes.