C++20 - The Complete Guide: Errata of First Edition |
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.
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 + falseint 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; // ERRORThe 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 compileOnly 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:
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 /