Daily bit(e) of C++ | Learn Modern C++ 3/N
Daily bit(e) of C++ #104 , A Modern-only C++ course (including C++23), part 3 of N
Welcome to the third lesson of the Learn Modern C++ sub-series. Today we will take a crash course in types.
If you missed the previous lesson, check it out here:
One distinguishing aspect of C++ is that it treats library types (including user-defined ones) like built-in types. For example, library types can define what happens when one variable is assigned to another or how comparisons and arithmetic operations work.
Note that there are subtle behavioural differences between built-in and library types, but nothing you need to worry about for this course and even for intermediate use cases.
If you finished homework for the previous lesson, you already came across some of the basic types, and I even mentioned one of the library types (std::vector) in one of the examples.
Bool
The bool is a boolean type with only two valid values, true and false.
True maps to 1 and false maps to 0. This behaviour is often used for various tricks which you should avoid. Explicit code is always better.
Integer types
Integer types cover signed integers: int, and unsigned integers: unsigned int (or simply “unsigned”). You have already seen both in the previous lesson.
Integer types can be further modified by the modifiers: “short”, “long”, and “long long”, creating a hierarchy of types with increasing bit count. For this course, we will stick with a fixed-width integer type: int64_t.
If you are interested in the bit-width of the different integer types, this page offers an excellent overview: https://en.cppreference.com/w/cpp/language/types#Properties.
While technically optional, the int64_t type will map to your local platform's 64-bit signed integer type.
Floating point
Finally, C++ offers three floating point types: float, double and long double. The encoding follows IEEE-754 where supported (most platforms do).
Arrays
For arrays, we must go to our first library type: std::vector. Because std::vector is a class template, we need to introduce a new syntax. Templates are effectively parametrized types. In this case, the parameter we care about is the element type.
Note that accessing nonexistent elements makes your program malformed, so always ensure the index is valid when accessing elements by index.
Errors like these are why we test homework with the “addrsan” and “ubsan” configs, which include the address sanitizer and undefined behaviour sanitizer. For example, the address sanitizer will catch this type of error, and the output will look something like this:
As I mentioned, library types can specify the behaviour of operators (such as the element access operator); on top of that, they can also provide manipulation and querying operations through member functions:
The std::vector is the go-to type that you will be using all the time. As such, I will skip ahead and mention an important aspect of std::vector: iterator invalidation.
When we push new elements into a std::vector, it might need to grow its internal storage. The visible side effect is that we no longer use iterators obtained before this reallocation, and the for-range loop relies on iterators for its internal implementation.
The critical takeaway is to avoid modifying the array as you iterate over it. Besides the risk of accessing invalid memory, it is also typically tough to reason about loops that modify the structure they are iterating over.
std::array
In rare situations, the complexity of std::vector might still be too much overhead. In such a case, we can use the statically sized std::array.
The second main use case where you might see std::array is compile-time programming, notably when we need to pass arrays from the compile-time part of the code to the runtime part; however, that is an advanced topic.
Strings
Strings in C++ are a fairly complex topic. For this course, we will be sticking with ASCII strings.
C++ does support Unicode; however, handling Unicode properly (even with language support) is an advanced topic, and the C++23 Unicode formatted output is not yet implemented in compilers.
The type for ASCII strings is std::string. It offers the same interface as std::vector with additional operations that are common when manipulating strings:
char
I skipped over the character type when I discussed the basic types, as you won’t be using it in isolation. However, you must know about it as it is the element type of std::string.
User types: aggregates
We will finish today’s lesson with the first category of user types. Aggregates (unsurprisingly) aggregate several variables into a single entity.
While simple, this user type is handy when we want to group specific data and treat them as one.
Templates
We have seen several templates today, so let’s briefly dip into this topic and look at how you can create a templated aggregate type.
Templates come in handy when creating generic types that capture structural information. For example, operations on an array behave the same no matter the element type (with some expert C++ caveats).
Commented example
In today’s commented example, we will find a path in a maze using a depth-first search. If you go to Compiler Explorer, you can play with the maze input and explore how the output changes.
Homework
The template repository with homework for this lesson is here: https://github.com/HappyCerberus/daily-bite-course-03.
As with all homework, you will need VSCode and Docker installed on your machine and follow the instructions from the first lesson.
The goal is to make all tests pass as described in the readme file.
I read your post every day when I wake up and I have learned a few tricks. Great works!