Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
josuttis_nm_c20_the_complete_guide.pdf
Скачиваний:
44
Добавлен:
27.03.2023
Размер:
5.85 Mб
Скачать

Chapter 21

Small Improvements for the Core Language

This chapter presents additional features and extensions that C++20 introduces for its core language that have not been covered in this book yet.

Additional small features for generic programming are described in the next chapter.

21.1 Range-Based for Loop with Initialization

C++17 introduced optional initialization for the if and switch control structures. C++20 now introduces such an optional initialization for the range-based for loop.

For example, the following code iterates over elements of a collection while incrementing a counter:

for (int i = 1; const auto& elem : coll) { std::cout << std::format("{:3}: {}\n", i, elem); ++i;

}

As another example, consider this code that iterates over the entries of the directory dirname:

for (std::filesystem::path p{dirname};

const auto& e : std::filesystem::directory_iterator{p}) { std::cout << " " << e.path().lexically_normal().string() << '\n';

}

As a third example, the following code locks a mutex while iterating over a collection:

for (std::lock_guard lg{collMx}; const auto& elem : coll) { std::cout << elem: << elem << '\n';

}

Note that the usual caveats for initializers in control structures apply: the initializer needs to declare a variable with a name. Otherwise, the initialization itself is an expression that creates and immediately

651

652

Chapter 21: Small Improvements for the Core Language

destroys a temporary object. As a consequence, initializing a lock guard without a name is a logical error because the guard would no longer lock when the iteration happens:

for (std::lock_guard{collMx}; const auto& elem : coll) {

// runtime ERROR

std::cout << elem: << elem << '\n';

// - no longer locked

}

 

The range-based for loop with initialization can also be used as a workaround for a bug in the range-based for loop. According to its specification, using the range-based for loop might result in a (fatal) runtime error when iterating over a reference to a temporary object.1 For example:

std::optional<std::vector<int>> getValues();

// forward declaration

 

for (int i : getValues().value()) {

// fatal runtime ERROR

...

 

 

}

 

 

Using the range-based for loop with initialization avoids this problem:

 

 

std::optional<std::vector<int>> getValues();

// forward declaration

 

for (auto&& optColl = getValues(); int i : optColl) {

// OK

 

...

 

 

}

 

 

In the same way, you can fix a broken iteration using a span:

 

 

for (auto elem : std::span{getCollOfConst()}) ...

// fatal runtime error

for (auto&& coll = getCollOfConst(); auto elem : std::span{coll}) ...

// OK

21.2 using for Enumeration Values

Assume we have a scoped enumeration type (declared with enum class): enum class Status{open, progress, done = 9};

In contrast to unscoped enumeration types (enum without class), the values of this type need a qualification with their type name:

auto

x

=

Status::open;

// OK

auto

x

=

open;

// ERROR

However, qualifying each value all the time can become a bit tedious in some contexts where it is clear that we have no conflicts. To make the use of scoped enumeration types more convenient, you can now use a using enum declaration.

1The problem has been known since 2009 (see http://wg21.link/cwg900). However, the C++ standards committee has so far not been willing to fix this bug as proposed, for example, in http://wg21.link/p2012.

21.2 using for Enumeration Values

653

A typical example is a switch over all possible enumeration values. You can now implement it as follows:

void print(Status s)

{

switch (s) {

using enum Status; // make enum values available in current scope case open:

std::cout << "open"; break;

case progress:

std::cout << "in progress"; break;

case done:

std::cout << "done"; break;

}

}

As long as no other symbol with the name open, progress, or done is declared in the scope of print(), this code works fine.

You can also use multiple using declarations for specific enumeration values now: void print(Status s)

{

switch (s) {

using Status::open, Status::progress, Status::done; case open:

std::cout << "open"; break;

case progress:

std::cout << "in progress"; break;

case done:

std::cout << "done"; break;

}

}

That way, you know exactly which names you make available in the current scope.

Note that you can also use the using declaration for unscoped enumeration types. It is not necessary, but that way, you do not have to know how an enumeration type is defined:

enum Status{open, progress, done = 9};

// unscoped enum

auto

s1

= open;

// OK

auto

s2

= Status::open;

// OK

654

 

Chapter 21: Small Improvements for the Core Language

using enum Status;

// OK, but no effect

auto s3

= open;

// OK

auto s4

= Status::open;

// OK

21.3Delegating Enumeration Types to Different Scopes

Using enum declarations can also be used to delegate enumeration values to different scopes. For example:

namespace MyProject { class Task { public:

enum class Status{open, progress, done = 9}; Task();

...

};

using enum Task::Status;

// expose the values of Status to MyProject

}

 

auto x = MyProject::open;

// OK: x has value MyProject::Task::open

auto y = MyProject::done;

// OK: y has value MyProject::Task::done

Please note that the using enum declaration exposes only the values, not the type:

MyProject::Status s;

// ERROR

To expose both the type and its values, you also need an ordinary using declaration (type alias):

namespace MyProject {

 

using Status = Task::Status;

// expose the type Task::Status

using enum Task::Status;

// expose the values of Task::Status

}

 

MyProject::Status s = MyProject::done; // OK

For exposed enum values, even argument-dependent lookup (ADL) works fine. For example, we can extend the example above as follows:

namespace MyProject {

void foo(MyProject::Task::Status) {

}

}

namespace MyScope {

using enum MyProject::Task::Status; // OK

}

foo(MyProject::done); // OK: calls MyProject::foo() with MyProject::Task::Status::done