Reader small image

You're reading from  Modern C++ Programming Cookbook - Third Edition

Product typeBook
Published inFeb 2024
PublisherPackt
ISBN-139781835080542
Edition3rd Edition
Right arrow
Author (1)
Marius Bancila
Marius Bancila
author image
Marius Bancila

Marius Bancila is a software engineer with two decades of experience in developing solutions for line of business applications and more. He is the author of The Modern C++ Challenge and Template Metaprogramming with C++. He works as a software architect and is focused on Microsoft technologies, mainly developing desktop applications with C++ and C#. He is passionate about sharing his technical expertise with others and, for that reason, he has been recognized as a Microsoft MVP for C++ and later developer technologies since 2006. Marius lives in Romania and is active in various online communities.
Read more about Marius Bancila

Right arrow

Using the indirection pattern for preprocessor stringification and concatenation

The C++ preprocessor provides two operators for transforming identifiers to strings and concatenating identifiers together. The first one, operator #, is called the stringizing operator, while the second one, operator ##, is called the token-pasting, merging, or concatenating operator. Although their use is limited to some particular cases, it is important to understand how they work.

Getting ready

For this recipe, you need to know how to define macros using the preprocessing directive #define.

How to do it...

To create a string from an identifier using the preprocessing operator #, use the following pattern:

  1. Define a helper macro taking one argument that expands to #, followed by the argument:
#define MAKE_STR2(x) #x
  1. Define the macro you want to use, taking one argument that expands to the helper macro:
#define MAKE_STR(x) MAKE_STR2(x)

To concatenate identifiers together using the preprocessing...

Performing compile-time assertion checks with static_assert

In C++, it is possible to perform both runtime and compile-time assertion checks to ensure that specific conditions in your code are true. Runtime assertions have the disadvantage that they are verified late when the program is running, and only if the control flow reaches them. There is no alternative when the condition depends on runtime data; however, when that is not the case, compile-time assertion checks are to be preferred. With compile-time assertions, the compiler is able to notify you early in the development stage with an error that a particular condition has not been met. These, however, can only be used when the condition can be evaluated at compile time. In C++11, compile-time assertions are performed with static_assert.

Getting ready

The most common use of static assertion checks is with template metaprogramming, where they can be used for validating that preconditions on template types are met (examples can include...

Conditionally compiling classes and functions with enable_if

Template metaprogramming is a powerful feature of C++ that enables us to write generic classes and functions that work with any type. This is a problem sometimes because the language does not define any mechanism for specifying constraints on the types that can be substituted for the template parameters. However, we can still achieve this using metaprogramming tricks and by leveraging a rule called substitution failure is not an error, also known as SFINAE. This rule determines whether the compiler discards, from the overloaded set, a specialization when substituting the explicitly specified or deduced type for the template parameter when it fails, instead of generating an error. This recipe will focus on implementing type constraints for templates.

Getting ready

Developers have used a class template usually called enable_if for many years in conjunction with SFINAE to implement constraints on template types. The enable_if...

Selecting branches at compile time with constexpr if

In the previous recipes, we saw how we can impose restrictions on types and functions using static_assert and std::enable_if and how these two are different. Template metaprogramming can become complicated and cluttered when we use SFINAE and std::enable_if to define function overloads or when we write variadic function templates. A new feature of C++17 is intended to simplify such code; it is called constexpr if, and it defines an if statement with a condition that is evaluated at compile time, resulting in the compiler selecting the body of a branch or another in the translation unit. Typical usage of constexpr if is for simplification of variadic templates and std::enable_if-based code.

Getting ready

In this recipe, we will refer to and simplify the code written in previous recipes. Before continuing with this recipe, you should take a moment to go back and review the code we have written in the previous recipes, as follows:

  • The...

Providing metadata to the compiler with attributes

C++ has been very deficient when it comes to features that enable reflection or introspection on types or data or standard mechanisms to define language extensions. Because of that, compilers have defined their own specific extensions for this purpose. Examples include the VC++ __declspec() specifier or the GCC __attribute__((...)). C++11, however, introduces the concept of attributes, which enable compilers to implement extensions in a standard way or even embedded domain-specific languages. The new C++ standards define several attributes all compilers should implement, and that will be the topic of this recipe.

How to do it...

Use standard attributes to provide hints for the compiler about various design goals such as in the scenarios listed here, but not only these:

  • To ensure that the return value from a function cannot be ignored, declare the function with the [[nodiscard]] attribute. In C++20, you can specify a string literal,...

Finding elements in a range

One of the most common operations we do in any application is searching through data. Therefore, it is not surprising that the standard library provides many generic algorithms for searching through standard containers, or anything that can represent a range and is defined by a start and a past-the-end iterator. In this recipe, we will see what these standard algorithms are and how they can be used.

Getting ready

For all the examples in this recipe, we will use std::vector, but all the algorithms work with ranges defined by a begin and past-the-end, either input or forward iterators, depending on the algorithm (for more information about the various types of iterators, see the Writing your own random access iterator recipe, later in this chapter). All these algorithms are available in the std namespace in the <algorithm> header.

How to do it...

The following is a list of algorithms that can be used for finding elements in a range:

...

Sorting a range

In the previous recipe, we looked at the standard general algorithms for searching in a range. Another common operation we often need to do is sorting a range because many routines, including some of the algorithms for searching, require a sorted range. The standard library provides several general algorithms for sorting ranges, and in this recipe, we will see what these algorithms are and how they can be used.

Getting ready

The sorting general algorithms work with ranges defined by a start and end iterator and, therefore, can sort standard containers, arrays, or anything that represents a sequence and has random iterators available. However, all the examples in this recipe will use std::vector.

How to do it...

The following is a list of standard general algorithms for searching a range:

  • Use std::sort() for sorting a range:
    std::vector<int> v{3, 13, 5, 8, 1, 2, 1};
    std::sort(v.begin(), v.end());
    // v = {1, 1, 2, 3, 5, 8, 13...

Initializing a range

In the previous recipes, we explored the general standard algorithms for searching in a range and sorting a range. The algorithms library provides many other general algorithms, and among them are several that are intended for filling a range with values. In this recipe, you will learn what these algorithms are and how they should be used.

Getting ready

All the examples in this recipe use std::vector. However, like all the general algorithms, the ones we will see in this recipe take iterators to define the bounds of a range and can therefore be used with any standard container, arrays, or custom types representing a sequence that have forward iterators defined.

Except for std::iota(), which is available in the <numeric> header, all the other algorithms are found in the <algorithm> header.

How to do it...

To assign values to a range, use any of the following standard algorithms:

  • std::fill() to assign a value to all the...

Using set operations on a range

The standard library provides several algorithms for set operations that enable us to do unions, intersections, or differences of sorted ranges. In this recipe, we will see what these algorithms are and how they work.

Getting ready

The algorithms for set operations work with iterators, which means they can be used for standard containers, arrays, or any custom type representing a sequence that has input iterators available. All the examples in this recipe will use std::vector.

For all the examples in the next section, we will use the following ranges:

std::vector<int> v1{ 1, 2, 3, 4, 4, 5 };
std::vector<int> v2{ 2, 3, 3, 4, 6, 8 };
std::vector<int> v3;

In the following section, we will explore the use of the standard algorithm for set operations.

How to do it...

Use the following general algorithms for set operations:

  • std::set_union() to compute the union of two ranges into a third range: ...

Using iterators to insert new elements into a container

When you’re working with containers, it is often useful to insert new elements at the beginning, end, or somewhere in the middle. There are algorithms, such as the ones we saw in the previous recipe, Using set operations on a range, that require an iterator to a range to insert into, but if you simply pass an iterator, such as the one returned by begin(), it will not insert but overwrite the elements of the container. Moreover, it’s not possible to insert at the end by using the iterator returned by end(). In order to perform such operations, the standard library provides a set of iterators and iterator adapters that enable these scenarios.

Getting ready

The iterators and adapters discussed in this recipe are available in the std namespace in the <iterator> header. If you include headers such as <algorithm>, you do not have to explicitly include <iterator>.

How to do it...

Use the...

Writing your own random-access iterator

In the first chapter, we saw how we can enable range-based for loops for custom types by implementing iterators, as well as free begin() and end() functions to return iterators to the first and one-past-the-last element of the custom range. You might have noticed that the minimal iterator implementation that we provided in that recipe does not meet the requirements for a standard iterator. This is because it cannot be copy constructible or assigned and cannot be incremented. In this recipe, we will build upon that example and show you how to create a random-access iterator that meets all requirements.

Getting ready

For this recipe, you should know the types of iterators the standard defines and how they are different. A good overview of their requirements is available at http://www.cplusplus.com/reference/iterator/.

To exemplify how to write a random-access iterator, we will consider a variant of the dummy_array class used in the...

Container access with non-member functions

Standard containers provide the begin() and end() member functions for retrieving iterators for the first and one-past-last elements of the container. There are actually four sets of these functions. Apart from begin()/end(), containers provide cbegin()/cend() to return constant iterators, rbegin()/rend() to return mutable reverse iterators, and crbegin()/crend() to return constant reverse iterators. In C++11/C++14, all these have non-member equivalents that work with standard containers, arrays, and any custom type that specializes them. In C++17, even more non-member functions have been added: std::data(), which returns a pointer to the block of memory containing the elements of the container; std::size(), which returns the size of a container or array; and std::empty(), which returns whether the given container is empty. These non-member functions are intended for generic code but can be used anywhere in your code. Moreover, in C++20,...

Selecting the right standard containers

The standard library contains a variety of containers for meeting multiple and various needs. There are sequence containers (in which elements are arranged in a certain position), container adapters (that provide a different interface for sequential containers), associative containers (in which the order is given by a key associated with an element), unordered associative containers (in which the elements do not follow a certain order). Selecting the right container for a given task is not always straight forward. This recipe will provide guidelines to help you decide which one to use for what purpose.

How to do it…

To decide which standard container you should use, consider the following guidelines:

  • Use std::vector as the default container, when no other specific requirements exist.
  • Use std::array when the length of a sequence is fixed and known at compile time.
  • Use std::deque if you frequently need to...
lock icon
The rest of the chapter is locked
You have been reading a chapter from
Modern C++ Programming Cookbook - Third Edition
Published in: Feb 2024Publisher: PacktISBN-13: 9781835080542
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Marius Bancila

Marius Bancila is a software engineer with two decades of experience in developing solutions for line of business applications and more. He is the author of The Modern C++ Challenge and Template Metaprogramming with C++. He works as a software architect and is focused on Microsoft technologies, mainly developing desktop applications with C++ and C#. He is passionate about sharing his technical expertise with others and, for that reason, he has been recognized as a Microsoft MVP for C++ and later developer technologies since 2006. Marius lives in Romania and is active in various online communities.
Read more about Marius Bancila