Josuttis C++20  C++20 - The Complete Guide: Errata of First Edition

C++20 - The Complete Guide

Errata of the First Edition

August 1, 2024

This is the errata of the 1st printing of the book C++20 - The Complete Guide by Nicolai M. Josuttis.
It covers all errors that were found.

The errata is organized in the following way:
- The first part lists technical errors
- The second part lists typos

Usually the latest electronic version covers all issues of this errata.


Errors

Page 17, 1.4 Overload Resolution with Rewritten Expressions: 2024-07-31

In section Calling Equality Operators at the end of what is tried to call for x!=y, replace:

!x.operator==(y)   // calling member operator== generated by operator<=> for x
!y.operator==(x)   // calling member operator== generated by operator<=> for y

by:

!y.operator==(x)   // calling a member operator== for y after reordering operands
!operator==(y, x)  // calling a free-standing operator== for y and x after reordering operands

In section Calling Relational Operators at the end of what is tried to call for x<=y, add:

0 <= operator<=>(y, x) // calling a free standing operator<=> for y and x after reordering operands

Page 22, 1.6.1 Delegating Free-Standing Comparison Operators: 2022-11-14

The code for the feature test macro is missing:

#ifndef __cpp_impl_three_way_comparison
bool operator==(int i, const MyType& t) {
  return t == i; // OK with C++17
}
#endif

Page 73, 4.4.3 Compound Requirements: 2024-07-31

Replace

You could also specify this as follows:
  { &x } -> std::is_pointer_v<>;

by the following:

To require that operator & really yields a raw pointer, you need a concept for the necessary type trait:
 template<typename T> concept IsPointer = std::is_pointer_v<T>;

 ...
   { &x } -> IsPointer;

Page 359, 11.3.1, Table 11.1. Standard duration types since C++20: 2023-08-05

The literals d and y yield type std::chrono::day and std::chrono::year , respectively (without s). Thus, they yield a caledrical type rather than a duration type. Therefore the entries d and y are wrong in column "Literal" the table.

Page 432, 13.2.1 Example of Using Counting Semaphores: 2024-08-01

The queue initialization does only initialize with characters from 'a' to 'y'. Use the following fix:

for (int i = 0; i < 1000; ++i) {
  values.push(static_cast<char>('a' + (i % ('z'-'a' + 1))));
}

Page 486, 14.3.1 Using co_yield, coro/generator.hpp: 2024-08-01

The returm type of operator* in Generatror::iterator must be T instead of int:

template<typename T>
class [[nodiscard]] Generator {
  ...
  struct iterator {
    ...
    T operator*() const {
      assert(hdl != nullptr);
      return hdl.promise().coroValue;
   }

   ...
  };
  ...
};

Page 495, 14.4.3 Resuming Sub-Coroutines: 2024-08-01

There is an severe runtime error in the example (available in coro/corotasksub.hpp). We might use data of a destroyed coroutine.

To fix this bug, replace:

// find deepest sub-coroutine not done yet:
CoroHdl innerHdl = hdl;
while (innerHdl.promise().subHdl && !innerHdl.promise().subHdl.done()) {
  innerHdl = innerHdl.promise().subHdl;
}

by the following code to deal with done sub-coroutines:

// find deepest sub-coroutine not done yet:
CoroHdl innerHdl = hdl;
while (innerHdl.promise().subHdl) {
  if (innerHdl.promise().subHdl.done()) {
    innerHdl.promise().subHdl = nullptr; // remove sub-coroutine if done
    break;
  }

  innerHdl = innerHdl.promise().subHdl;
}

Page 546, 15.9.2 A Thread Pool for Coroutine Tasks: coro/coropool.hpp: 2024-08-01

The function runTask() was screwed up so that it even didn't compile.

The content has to be as follows:

void runTask(CoroPoolTask&& coroTask) noexcept {
  auto hdl = std::exchange(coroTask.hdl, nullptr); // pool takes ownership of hdl
  if (hdl.done()) {
    hdl.destroy(); // OOPS, a done() coroutine was passed
  }
  else {
    runCoro(hdl);  // schedule coroutine in the pool
  }
}

Page 601, 18.1 Keyword constinit: 2024-07-30

There is an inline missing in the first example:

class MyType {
  static inline constinit long max = sizeof(int) * 1000;
  // ...
};

Page 616-617, 18.4.1 std::is_constant_evaluated() in Detail: 2024-07-31

Both example are a bit screwed up. Direct fixes are:

constexpr bool isConstEval() {
  return std::is_constant_evaluated();
}

bool g1 = isConstEval();            // true
const bool g2 = isConstEval();      // true
static bool g3 = isConstEval();     // true
static int g4 = g1 + isConstEval(); // true + false

int main()
{
  bool l1 = isConstEval();                            // false
  const bool l2 = isConstEval();                      // true
  static bool l3 = isConstEval();                     // true
  int l4 = g1 + isConstEval();                        // true + false
  const int l5 = g1 + isConstEval();                  // true + false
  static int l6 = g1 + isConstEval();                 // true + false
  int l7 = isConstEval() + isConstEval();             // false + false
  const auto l8 = isConstEval() + 42 + isConstEval(); // true + 42 + true
}

and:

bool runtimeFunc() {
  return std::is_constant_evaluated(); // always false
}
constexpr bool constexprFunc() {
  return std::is_constant_evaluated(); // may be false or true
}
consteval bool constevalFunc() {
  return std::is_constant_evaluated(); // always true
}

void foo()
{
  bool b1 = runtimeFunc();               // false
  bool b2 = constexprFunc();             // false
  bool b3 = constevalFunc();             // true

  static bool sb1 = runtimeFunc();       // false
  static bool sb2 = constexprFunc();     // true
  static bool sb3 = constevalFunc();     // true
  constexpr bool cb0 = runtimeFunc();    // ERROR

  const bool cb1 = runtimeFunc();        // false
  const bool cb2 = constexprFunc();      // true
  const bool cb3 = constevalFunc();      // true

  int y = 42;
  static int sb4 = y + runtimeFunc();    // 42 + false
  static int sb5 = y + constexprFunc();  // 42 + false
  static int sb6 = y + constevalFunc();  // 42 + true
  const int cb4 = y + runtimeFunc();     // 42 + false
  const int cb5 = y + constexprFunc();   // 42 + false
  const int cb6 = y + constevalFunc();   // 42 + false
}

However, there is a better documenting example, which I will use in later printings of the book.

Page 619, 18.4 is_constant_evaluated() and Operator ?: 2024-07-31

The whole peragraph has some typos, which makes is unnecessarily hard to head. Here is the improved version:

In the C++20 standard, there is an interesting example to clarify the way std::is_constant_evaluated()
can be used. Slightly modified, it looks as follows:

int sz = 10;
constexpr int sz1 = std::is_constant_evaluated() ? 20 : sz; // true, so 20
constexpr int sz2 = std::is_constant_evaluated() ? sz : 20; // ERROR

The reason for this behavior is as follows:
• The initialization of sz1 and sz2 is either static initialization or dynamic initialization.
• For static initialization, the initializer must be constant. Therefore, the compiler attempts to evaluate the
initializer with std::is_constant_evaluated() treated as a constant of value true.

– With sz1, that succeeds. The result is 20, and that is a constant. Therefore, sz1 is a constant initialized
with 20.

– With sz2, the result is sz, which is not a constant. Therefore, sz2 is (notionally) dynamically initialized.
The previous result is therefore discarded and the initializer is evaluated with
std::is_constant_evaluated() producing false instead. Therefore, the expression to initialize
sz2 is also 20.
However, sz2 is not necessarily a constant because std::is_constant_evaluated() is not necessarily
a constant expression during this evaluation. Therefore, the initialization of sz2 with this 20
does not compile.

Using const instead of constexpr makes the situation even more tricky:

int sz = 10;
const int sz1 = std::is_constant_evaluated() ? 20 : sz; // true, so 20
const int sz2 = std::is_constant_evaluated() ? sz : 20; // also 20
double arr1[sz1]; // OK
double arr2[sz2]; // may or may not compile

Only sz1 is a compile-time constant and can always be used to initialize an array. For the reason described
above, sz2 is also initialized to 20. However, because the initial values is not necessarily a constant, the
initialization of arr2 may or may not compile (depending on the compiler and optimizations used).

Page 631, 19.1 New Types for Non-Type Template Parameters: 2024-08-01

The definition of structural/literal type is not exactly right. So replace the definition of literal type as follows:

Page 635, 19.1.2 Objects as Non-Type Template Parameters: 2024-08-01

The definition of structural/literal type is not exactly right. So replace the definition of literal type as follows:


Typos

Page 4, 1.1.2 Defining Comparison Operators Since C++20

s/MyType a;/TypeA a;/
s/MyType b;/TypeB a;/

Page 12, 1.2.5 Dealing with Multiple Ordering Criteria

S / of the internal comparsions to values / of the internal comparisons to values /

Page 19, 1.5.1 compare_three_way

s/ return std::compare_three_way{}(val<=>v.val); / return std::compare_three_way{}(val, v.val); /

Page 53, 3.3.3 Using Requirements to Call Different Functions

Remove template<typename Coll, typename T>
before  void add(auto& coll, const auto& val)

Page 64, 3.6 Afternotes

twice: s/ various imrovement were / various improvements were /

Page 86, 4.7.2 Defining Commutative Concepts:

s/ Now, the order of the parameters no longer matters for IsSame<>: / Now, the order of the parameters no longer matters for SameAs<>: /

Page 101, 5.3.2 Concepts for Pointer-Like Objects

Twice: s/ void foo(InP inPos, OutP, outPos) { / void foo(InP inPos, OutP outPos) { /

Page 103, 5.3.3 Concepts for Iterators: std::output_iterator

s/ you can assign values of typeT. / you can assign values of type T. /

s/ std::indirectly_writable<Pos, I> is satisfied / std::indirectly_writable<Pos, T > is satisfied /

Page 107, 5.4.1 Basic Concepts for Callables

s / used as a as Boolean value / used as a Boolean value /

Page 130, 6.1.5 Range Definitions with Sentinels and Counts

s/ what was was passed / what was passed/

Page 157-159, 6.5.1 Generic Code for Both Containers and Views

s/ const& Does Not Work for All Views / Declaring Range Parameters as const& Does Not Work for Some Views /

s/ // not callable for all views / // not callable for some views /   (4 times)

s/ Non-const && DoesWork for All Views / Declaring Range Parameters as Non-const && /

Page 168, 6.6 Summary of All Container Idioms Broken By Views

s/ that we we can count on / that we can count on /

Page 168, 6.7 Afternotes

s/ Standard Template Llibrary / Standard Template Library /

Page 216, 8.3.3 Owning View

s/ we pass the a temporary container / we pass the temporary container /

Page 222, 8.4.1 Iota View, ranges/iotaview.cpp

s/ auto iv3 = std::views::iota(1); // -2 -1 0 1 ... / auto iv3 = std::views::iota(1); // 1 2 3 4 ... /

Page 234, 8.4.4 IStream View

s/ an string stream / a string stream /

Page 240, 8.4.6 Span

s/ underlying character sequence / underlying sequence of elements /

Pages 345 and 346, 11.1.1 Scheduling a Meeting on the 5th of Every Month: Other Ways to Initialize Calendrical Types:

s/ auto d1 = std::chrono::years{2021}/1/5; / auto d1 = std::chrono::year{2021}/1/5; /

s/ auto d2 = std::chrono:month{1}/5/2021; / auto d2 = std::chrono::month{1}/5/2021; /

s/ auto d3 = std::chrono:day{5}/1/2021; / auto d3 = std::chrono::day{5}/1/2021; /

s/ auto d5 = 5/1/2021; / auto d5 = 5d/1/2021; /

Page 365, 11.3.5 Time Type hh_mm_ss:

s/ std::chrono::floor<std::chrono:days>(tp) / std::chrono::floor<std::chrono::days>(tp) /

Page 366, 11.3.6 Hours Utilities

s/ std::chrono::make24(h, toPM) / std::chrono::make24(h, isPM) /

Page 449, 13.3.4 Thread Synchronization with Atomic Types

s/ One application of using using atomic wait() / One application of using atomic wait() /

s / Here is a example / Here is an example /

Page 568, 16.2.2 Using Implementation Units

s/ export module mod1; // module declaration / export module Mod1; // module declaration /

Page 611, 18.2.2 constexpr versus consteval

s/ must to perform its computing / must perform its computing /

Page 616, 18.4.1 std::is_constant_evaluated() in Detail

s/ or an constant initialization / or a constant initialization /

Page 631, 19.1 New Types for Non-Type Template Parameters:

s/ or and lvalue reference type / or an lvalue reference type /

Page 659, 21.4.2 Broken Backward Compatibility

s/ that we ha simply not enough time / , for which we simply had not enough time /

Page 671, 21.7 Feature Test Macros

An #endif is missing in the middle of the first example before the second #else.

Page 681, 22.4 Afternote:

s/ proposed by by Daveed Vandevoorde / proposed by Daveed Vandevoorde /

 


Home of the C++20 book