programming, tutorial,

Quick tour from C++98 to C++17

Carlos Carlos Follow Jan 09, 2020 · 13 mins read
Quick tour from C++98 to C++17
Share this

Wait, no more new? Wtf is auto? constexpr?! Why so many keywords?? What the hell happened with C++ since college?

The good old days of C++98

So, what nobody told me is this, the C++ is an International Standard, and as such, it evolves (gradually and upon many discussions). What we learn in college is actually a set of rules stablished by the ISO revision of 98 in which we have destructors, templates, our beloved STL with common containers and algorithms, string, I/O streams, among other things. However, C++ is NOT static and major changes happened since then, some of them can make you feel it’s not even the same language anymore.

If you are like me and started programming in other languages to follow the tides of the market and now you are trying to code in C++ again, for fun or business, you may have a hard time with the new concepts that arrived to the language, let me try to make it easier for you, putting together the information I found useful during my research.

So, what’s changed?

Well… a lot! Kidding, but not kidding Let’s break the main changes into pieces and follow along with a simple sample code, so in the next sections I’m gonna try to illustrate how the same code would be written in different C++ revisions introducing the main features of each one.

Our sample code will be a program that reads input from the standard input and outputs a map of word frequency, ignoring any non-alphabetic character and, by convenience, transforming all characters to lower case.

In C++98 it would look like this:

#include <iostream>
#include <map>
#include <algorithm>
#include <iterator>
#include <cctype>

// this is a "functor", a class that implements the operator () and can
// therefore be called in a function-like way
class frequency_counter {
  // we don't have unordered_map yet, only the good old map with O(log n)
  std::map<std::string, int>* map;
  std::string current_word;

  public:
  frequency_counter(std::map<std::string, int>* m) : map(m), current_word() {}
  void operator()(const char letter) {
    // in case it is a alpha character
    if (isalpha(letter)) {
      // convert to lower case
      current_word += tolower(letter);
    } else {
      // if there is a word built
      if (current_word.size() > 0) {
        // increment mapping for current word
        (*map)[current_word]++;
        // reset our buffer
        current_word = "";
      }
    }
  }
};

int main() {
  std::map<std::string, int> map;
  frequency_counter functor(&map);
  
  // word counting using for_each and the frequency_counter functor
  std::for_each(std::istream_iterator<char>(std::cin >> std::noskipws),
                std::istream_iterator<char>(),
                functor);

  // old way to iterate over a collection
  for (std::map<std::string, int>::iterator it = map.begin();
       it != map.end();
       ++it)
    std::cout << it->first << ": " << it->second << std::endl;

  return 0;
}

Fairly simple, right? Moving on…

C++03

This realease is commonly known as a bug fix release for the C++98 revision and included only one new feature, value initialization, as a side note, when most people refer to C++98 they are usually refering to C++03.

One valuable addition to the language came in the form of Technical Report 1 (TR1 for short) around 2005. TR1 is not a standard, it is (as the name says) a technical revision that proposed several additions to the C++ Standard Library, these additions included smart pointers, hash tables, regular expressions, among others. Many of these additions became part of the C++11 later, but before that happened, vendors started to use this document as a guide to create extensions, and even though these features weren’t needed to create standard compliant distributions of compilers or libraries, several distributors implemented them under the namespace std::tr1 to distinguish these features from the then-current standard library.

Let’s make use of these features on our example, still following the standard C++03 but including some of the features in the tr1.

#include <iostream>
#include <algorithm>
#include <tr1/unordered_map>
// regex in tr1 was never fully functional
#include <tr1/regex>

struct frequency_counter {
  std::tr1::unordered_map<std::string, int>* map;

  frequency_counter(std::tr1::unordered_map<std::string, int>* m) : map(m) {}
  void operator()(const char* word) {
    (*map)[word]++;
  }

};

int main (int argc, char* argv[]) {
  std::tr1::unordered_map<std::string, int> map;
  frequency_counter functor(&map);

  std::for_each(argv + 1, argv + argc, functor);

  for (std::tr1::unordered_map<std::string, int>::iterator it = map.begin();
       it != map.end();
       ++it)
    std::cout << it->first << ": " << it->second << std::endl;

  return 0;
}

C++11

Most of the changes to the language are credited to this release. It wasn’t only the new features, but the language itself evolved in a manner that C++11 gave to the programmer a more high-level environment to think and solve problems.

Two of the most significant changes that arrived in C++11 were the move semantics and the rule of 5 (updated version of the rule of 3), which I am not going to cover here because they are a whole other topic by themselves.

  • auto
  • ranged for loops
  • lambdas
  • variadic templates
  • unique_ptr/shared_ptr
  • constexpr
#include <iostream>
// now part of the stdlib \o/
#include <unordered_map>
#include <regex>

int main () {
  // unordered_map enters, O(1) achieved \o/
  std::unordered_map<std::string, int> umap;
  // regex become part of the c++ std lib
  std::regex re("[a-z]+");

  // initialize text with stream iterators
  std::string text(std::istream_iterator<char>(std::cin >> std::noskipws),
                   std::istream_iterator<char>());

  // lowercase everything
  std::transform(text.begin(), text.end(), text.begin(),
                 // very lambda
                 [](const unsigned char l){ return std::tolower(l); });

  // find and count words only
  std::for_each(std::sregex_iterator(text.begin(), text.end(), re),
                std::sregex_iterator(),
                // much useful
                [&umap](const std::smatch& match){ umap[match.str()]++; });

  // ordering
  std::vector<std::pair<std::string, int>> freq{umap.begin(), umap.end()};
  std::sort(freq.begin(), freq.end(),
            // many utilities! WOW!
            [](const std::pair<std::string, int>& i, const std::pair<std::string, int>& j){ return i.second > j.second; });

  // print map
  // now we can use auto, specially for variables we don't care
  // like iterating on our frequency map using the ranged for loop
  for (const auto& pair : freq)
    std::cout << pair.first << ": " << pair.second << std::endl;

  return 0;
}

C++14

Generally speaking, enhancements in the C++11

  • auto in function return types
  • generic lambda functions
  • make_unique/make_shared (new or delete should not be seen in code anymore)
  • constexpr more flexible
#include <iostream>
#include <unordered_map>
#include <regex>

template<typename ...Args>
void print_map(Args&&... args) {
  (std::cout << ... << args) << '\n';
}

int main () {
  std::unordered_map<std::string, int> umap;
  std::regex re("[a-z]+");

  // initialize text with stream iterators
  std::string text(std::istream_iterator<char>(std::cin >> std::noskipws),
                   std::istream_iterator<char>());

  // lowercase everything
  std::transform(text.begin(), text.end(), text.begin(),
                 [](const unsigned char l){ return std::tolower(l); });

  // find and count words only
  std::for_each(std::sregex_iterator(text.begin(), text.end(), re),
                std::sregex_iterator(),
                [&umap](const auto& match){ umap[match.str()]++; });

  // ordering
  std::vector<std::pair<std::string, int>> freq{umap.begin(), umap.end()};
  std::sort(freq.begin(), freq.end(),
            [](const auto& i, const auto& j){ return i.second > j.second; });

  // print map
  for (const auto& [key, value] : freq)
    print_map(key, '\t',  value);

  return 0;
}

C++17

  • constexpr support in stdlib
  • constexpr lambdas
  • std::string_view
  • class template argument deduction
  • fold expressions
  • structured bindings
  • if-init expressions
#include <iostream>
#include <unordered_map>
#include <regex>

template<typename ...Args>
void print_map(Args&&... args) {
  (std::cout << ... << args) << '\n';
}

int main () {
  std::unordered_map<std::string, int> umap;
  std::regex re("[a-z]+");

  // initialize text with stream iterators
  std::string text(std::istream_iterator<char>(std::cin >> std::noskipws),
                   std::istream_iterator<char>());

  // lowercase everything
  std::transform(text.begin(), text.end(), text.begin(),
                 [](const unsigned char l){ return std::tolower(l); });

  // find and count words only
  std::for_each(std::sregex_iterator(text.begin(), text.end(), re),
                std::sregex_iterator(),
                [&umap](const auto& match){ umap[match.str()]++; });

  // ordering
  std::vector<std::pair<std::string, int>> freq{umap.begin(), umap.end()};
  std::sort(freq.begin(), freq.end(),
            [](const auto& i, const auto& j){ return i.second > j.second; });

  // print map
  for (const auto& [key, value] : freq)
    print_map(key, '\t',  value);

  return 0;
}

EXTRA: C++20

Future (Feb, 2020), introduces modules, coroutines, std::format, among others. Coroutines, Modules, Concepts, abbreviated function templates, Ranges, uniform container erasure (std::erase/std::erase_if).

References, credits and things you may want to check out

Join Newsletter
Get the latest news right in your inbox. We never spam!
Carlos
Written by Carlos Follow
Hi, I am Carlos, SWE 3x @ Google, 2x @ UNICEF and author of this blog. I like to make things work and hope you enjoy the experiences I am going to share here.