Skip to Content

Modern C++ for C Programmers: Part 6

Posted on

NOTE: If you like this stuff, come work with me over at PowerDNS - aspiring C++ programmers welcome!

In part 5 we discussed smart pointers, placement new and the powerful move constructor. As you may have gathered by now, parts 1 through 5 were a pitch to sell modern C++ to existing C specialists. To do so, I tried to show the best and most immediately useful parts of C++.

Note: part 1 is here.

As noted earlier, no language is perfect, and not all features of C++ are as good or as spectacular. In this final part of ‘Modern C++ for C Programmers’ you will find some of the ‘best of the rest’.

Finally, we finish this series with a list of recommended books to read.

Examples from this page can be found in rest.cc on the GitHub repository.

Static assert

Sometimes our code depends on certain things being true, like for example a struct being exactly 16 bytes long. The C preprocessor is still available in C++, but by design the preprocessor can’t execute code. static_assert can:

  static_assert(sizeof(int) == 4, "Int must be 4 bytes");

Static asserts are evaluated at compile time and leave no trace in the resulting object code. Static_asserts are great to test generic assumptions but truly shine when writing templatized code, where one of the template parameters for example must be a pointer:

#include <type_traits>

template<typename T>
bool isSet(const T& t)
{
  static_assert(std::is_pointer<T>(), "isSet requires a pointer");
  return t != nullptr;
}

Static asserts not only prevent bad things from compiling, they also allow code to self-document its intended use, turning a screen full of compilation errors into a helpful message.

Note that this code uses the type support function std::is_pointer. The library has far more tests that can be useful to write robust code.

constexpr

A constexpr is a piece of code that can be executed entirely at compile time, leaving its return value ready to use in the object code. This makes compiling slower, but the resulting executable faster (and smaller). It also feels very neat to get essentially static things done at compile time.

Practically speaking, it can also frequently prevent the need to autogenerate large sets of #defines for example - they can be calculated at compile time.

A simple example, a compile time htonl for use on little-endian systems:

constexpr uint32_t chtonl(uint32_t s)
{
    return 
        ((s & 0x000000FF) << 24) | ((s & 0x0000FF00) << 8)
      | ((s & 0xFF000000) >> 24) | ((s & 0x00FF0000) >> 8);
}

With some further work, it is possible to make a version that does the right thing on big-endian platforms as well.

The frozen library is a good example of what is possible at compile time:

#include <frozen/set.h>

constexpr auto avoidPorts = frozen::make_set<int>({25, 53, 80, 110, 443});

..

do {
	port = random() & 0xffff;
} while (avoidPorts.count(port));

The avoidPorts set is populated (and sorted!) at compile time. Frozen also offers perfect hashing for rapid keyword lookups.

constexpr further enables really const symbols that still show up in coredumps and backtraces - which is far superior to using a #define.

constexpr does not allow for arbitrary code to be executed at compile time because (understandably) there are a lot of constraints to make sure the output is really really const. C++17 has some additional constexpr features that make this easier.

Numeric limits

To find limits on numerical types, std::numeric_limits, can be used like this:

#include <limits>

int i;
cout << std::numeric_limits<int>::min() << endl;
cout << std::numeric_limits<decltype(i)>::max() << endl;

This prints the minimum and maximum allowed values of an int. Note that in the last line, we use decltype to get the type of i. decltype is sometimes necessary to make templates work. It does look a bit ugly but it leaves no trace in the resulting object code.

std::numeric_limits<> offers access to epsilon and many other attributes of a type in a uniform way that is way easier than looking up #defines like ULLONG_MAX.

You will not be surprised to learn that in modern C++, all the std::numeric_limits methods are constexpr.

iostreams

As mentioned in earlier parts, the C++ iostreams should not be seen as a replacement for either POSIX or C streams based i/o. The C++ iostreams are not very performant and can be utterly frustrating to work with. They still have their uses however.

#include <fstream>

...
ifstream in("/etc/passwd");

if (!in.is_open()) {
  std::cout << "failed to open\n";
} 
else {
  string line;
  int count = 0;
  while(getline(in, line)) 
    ++count;
  cout << "Read " << count << " lines\n";
}

If all you need to do is reading a (small) numbers of lines of text, iostreams are just fine. In terms of formatting output, this is by far not as easy as fprintf, and it is very easy to mess up even, since iostreams carry state. So if you modify the output of floats, you do so for subsequent floats too.

A useful component of the iostreams library is ostringstream which allows for the easy generation of strings. This a iostream that creates a string:

ostringstream msg;
msg << "The current temperature is " << temp << " degrees";
std::string status = msg.str();

However, frequently it may be easier to do:

string status = "The current temperature is " + to_string(temp);
status += " degrees";

std::to_string is a useful function in general. C++17 offers a very high performance allocation free variant that is locale independent called std::to_chars - for all your serialization needs.

To get printf-like behaviour, consider using boost::format which enables things like:

cout << boost::format("Temperature is %.02f\n") % temp;

boost::format goes beyond POSIX printf and has a neat syntax for absolute positioning of variables.

Measuring time

C++ is used heavily in particle physics, powering the foundational ROOT library which helped discover the Higgs Boson and much more. Particle physicists have very exacting needs in terms of timing, and in somewhat of a mixed blessing, they found a willing ear over at the C++ standards committee.

As a result, the C++ infrastructure for measuring times and durations is.. extravagant. Getting the current time(stamp) is not a cheap operation these days (in any language), since different cores or CPUs may be running at different speeds. Depending on your application, you may prefer a fast but potentially non-monotonous timesource over a slow but always correct one.

C++ currently offers us access to system_clock, steady_clock and high_resolution_clock. In the near future, this will be expanded to gps_clock, tai_clock and utc_clock, which seems to indicate the astronomers managed to infiltrate the C++ standards committee as well.

If all you want to do is measure how long something took, this is useful:

  using namespace std::chrono;

  auto start = system_clock::now();
 
  // do something

  auto end = system_clock::now();
 
  duration<double> elapsed_seconds = end-start;
  std::time_t end_time = system_clock::to_time_t(end);

  std::cout << "finished computation at " << std::ctime(&end_time)
            << "elapsed time: " << elapsed_seconds.count() << "s\n";

Note that it is also possible to get a duration in ‘nanoseconds’ expressed as an integer, or even in dedicated particle physics units like shakes. Details are here.

Scoped enums

I don’t know about you, but I’ve never been happy that enums, despite having a name, project into the scope of where they were defined. In other words, there can only be one global enum that defines BLUE.

Modern C++ has scoped enums, also known as class enums:

enum class Color { red, green = 20, blue };

Color r = Color::blue;

switch(r)
{
    case Color::red  : std::cout << "red\n";   break;
    case Color::green: std::cout << "green\n"; break;
    case Color::blue : std::cout << "blue\n";  break;
}

// int n = r; // error: no scoped enum to int conversion
int n = static_cast<int>(r); // OK, n = 21

Note how these enums populate names within their own name - Color::red versus red.

This example is from the most excellent cppreference.com site.

Modern enums are typesafe so you can’t accidentally assign a Color to (say) a Priority. These modern enums are better in almost every way compared to the classic ones.

Initializer lists

Initializer lists are remarkably simple yet powerful constructs. They have appeared in a few earlier examples, and they power initializations like:

  vector<int> vi{1, 2, 3, 4};
  vector<string> vs{"foo", "bar", "baz"};

When the compiler sees a braced-init-list in the right context, it will convert it into a std::initializer_list. This is a lightweight proxy container meant to pass lists around without copying them. It is not a storage mechanism itself.

In the two examples above, std::vector has a constructor that accepts initializer_lists compatible with the ones we created.

A neat use is within range-for iteration:

  for(auto a : { 1, 10, 15, 25} )
    data.printWeightedAverages(a); 

Or, if we have a bunch of vectors which all need to be averaged:

  vector<double> x, y, z;

  // x, y and z get filled with data

  for(auto& a : {x, y, z})
    a.doAverage();

Initializer lists save a lot of typing by making many initializations and iterations possible in-line.

Regular expressions, raw strings

Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems. – jwz

Despite this, regular expressions have their uses, and they are a standard part of many other languages too.

Again an example adapted from the excellent cppreference.com site:

  std::string s = R"(Some people, when confronted with a problem, think: 
"I know, I'll use regular expressions."
Now they have two problems.)";

Here we define a string to search. Note that I used the new C++ ‘raw’ string capability, whereby you start a string with R"( and end it with )", and in between everything gets copied. If you are worried about )" appearing in your string, you can also do:

  std::string s = R"foo(Some people, when confronted with a problem, think: 
"I know, I'll use regular expressions."
Now they have two problems.)foo";

We can now regex to our heart’s content:

  std::regex word_regex("(\\S+)");
  auto words_begin = 
        std::sregex_iterator(s.begin(), s.end(), word_regex);
  auto words_end = std::sregex_iterator();
 
  std::cout << "Found "
            << std::distance(words_begin, words_end)
            << " words\n";

  for_each(words_begin, words_end, [](const auto& w) {
    cout << "Word: " << w.str() << endl;
  });

Note that regular expressions aren’t tied to std::string, they work on any collection of characters. The result of a regular expression is exposed as a regular iterator, so it can also be traversed like that.

C++ regular expressions can match, search and replace, and support various regex dialects and case sensitivity options.

Default parameters, function overloading

If we want to get something done, sometimes we have more precise needs than at other times. For example:

Channel c;
Event e1, 2;

c.sendEvent(e1);  // use default priority
c.sendEvent(e2, Channel::Priority::High);

C++ has two facilities for supporting optional parameters to functions and methods. With default parameters, we can write:

class Channel
{
...
   void sendEvent(const Event& e, Priority p = Priority::Medium);

In this case, the Priority will be set to Priority::Medium if we omit it in the call to sendEvent.

Alternatively, let’s say the default priority is not known at compile time, we can do:

   void sendEvent(const Event& e);
   void sendEvent(const Event& e, Priority p);

In our sample above, e1 will get processed by the first function and e2 by the second. The first function could then lookup the runtime configured default priority.

std::optional (and boost::optional)

Sometimes we want to have parameters or values that may or may not be set. This has traditionally been implemented ‘in-band’ or with pointers. The in-band solution for example is to have a Priority called Default. Whenever a function spots this pseudo-priority, it replaces it by the configured default Priority. This is not quite satisfying however, since downstream code that does stuff like:

switch(prio) {
	case Priority::Low:
	...
	case Priority::Medium:
	...
	case Priority::High:
	...
}

Will now generate a warning that Priority::Default is not dealt with. And the compiler is not wrong - we’ve now mixed in a “magic” default Priority together with the real ones.

Another popular solution is passing pointers to values, where the convention then is that passing a NULL pointer means no value is set. While this works, it does create the lifetime issues associated with pointers, and we can’t take the pointer of Priority::High without storing it as a variable first.

To solve this problem, Boost has long offered boost::optional which in C++17 got adopted as std::optional (with some minor changes). It works like this:

  std::optional<Channel::Priority> p;
  if(!p)
    cout << "Priority not set" << endl;

  p = Channel::Priority::High;
  if(*p == Channel::Priority::High)
    cout << "Priority is now set to High" << endl;

std::optional values are unset by default, and can be assigned as if they are the type they host. To get the actual value of a std::optional variable, use the * operator. Our sample Channel from above can now be written as:

  void sendEvent(const Event& e, std::optional<Priority> p={})
  {
    if(!p)
      cout << "No priority passed" << endl;
    switch(*p) {
      case Priority::Low:
        cout << "Low!" << endl; break;
      case Priority::Medium:
        cout << "Medium!" << endl; break;
      case Priority::High:
        cout << "High!" << endl; break;
      ;
    }
  }

Where {} means the default is unset, which could also have been written as = std::nullopt. A useful std::optional method is value_or(x), which can be used like this: switch(p.value_or(Priority::Medium)). This makes sure we pick Priority::Medium as default of p is not set.

std::optional (and the Boost variant) are also useful to return an optional value, like for example a Policy that might or might not apply to a Channel:

class Channel
{
...
  std::optional<Policy> getPolicy(const Event& e)
  {
    auto pol = d_policies.find(e);
    if(pol == d_policies.end())
      return std::nullopt;
   
    return pol->second;
  }
...
};

if(auto policy = c.getPolicy(e)) {
  if(policy->check())
    c.sendEvent(e);
}

Like with passing parameters, this prevents the need for ‘null’ policies, returning pointers or passing references. std::optional meanwhile is fully stack based (allocation free), std::move aware and presents no runtime overhead.

Note that in C++17 the last part could even be written as:

if(auto policy = c.getPolicy(e); policy->check())
    c.sendEvent(e);

This is called if statement with initializer, and it works just like with for statements, and saves some typing.

Summarizing

I sincerely hope that these posts have given you a new appreciation of (modern) C++, and that if you make the jump into C++, this will make you a better programmer. I also hope you will have fun using this beast of a language!

Books to read

If you liked what you read about modern C++, here are some recommendations for further reading. With three notable exceptions, I would recommend skipping books that have not been updated to C++11 or later. The shortcomings of older C++ have ugly workarounds that may confuse you.

I’m indebted to the C++ community on Twitter that responded with many suggestions for new books. In that Twitter thread you’ll also find some additional books not listed below.

  • The C++ Programming Language (4th edition) by Bjarne Stroustrup. An astounding book. It has wisdom on every page. It also has 1400 pages. A hilarious review by Verity Stob can be found on The Register. In TCPL we read “C is not a big language, and it is not well served by a big book.“. Hence the 1400 pages for C++. Even though the book is far too huge to read from cover to cover (although I think I’ve now hit almost every page), everyone should have a copy. It does indeed explain all of C++ (up to 2011), and it does so pretty well. The book also is strong on C++ idioms that will make your life easier.
  • A Tour of C++ (2nd edition, 2018) by Bjarne Stroustrup. In 256 pages, this provides a cogent introduction to C++17. Make sure to get the second edition. This book overlaps with the mother of all C++ books, the C++ Programming Language, but unlike TC++PL (fourth edition), it covers C++14 and C++17.
  • Discovering Modern C++: An Intensive Course for Scientists, Engineers, and Programmers by Peter Gottschling. Covers C++14 and appears to do a great job of it. A sample chapter is provided here.
  • The C++ Standard Library: A Tutorial and Reference (2nd Edition) by Nicolai M. Josuttis. Covers the modern C++ library in exhaustive depth.
  • The Modern C++ Challenge by Marius Bancila. This recent and fun book covers modern C++ in a unique way. Most books on this list cover a sort of ethereal version of C++ with no contact with such mundane things as timezones, sockets, JSON or zipfiles. This book is explicitly a real world book and touches on the use of libraries, interactions with webservices and cryptography.
  • C++ Primer by Stanley Lippman, Josée Lajoie and Barbara E. Moo. The fifth edition is updated for C++11.
  • Effective C++ by Scott Meyers - lists common pitfalls, wonderful features and problematic areas to avoid. Does not cover C++11 or beyond, but still worth it.
  • More Effective C++ - more of the same. Still worth your while.
  • Effective STL - Effective C++, but then with a stronger focus on the standard library. Does not cover C++11.
  • Effective Modern C++ - C++14 era Effective C++. Somewhat unevenly edited, spending quite some pages on obscure issues, but still highly recommended.
  • Accelerated C++. This was a good C++ book, but it is now outdated. I am told that a second edition featuring modern C++ will appear in December 2018. Please check back then!

NOTE: If you like this stuff, come work with me over at PowerDNS - aspiring C++ programmers welcome!