📜 ⬆️ ⬇️

New C ++ 17 that everyone should use

Ladies and gentlemen, hello.

We have just completed the translation of an interesting book by Jacek Galovitsa about STL C ++ 17, which we hope to release the sooner the better.


Today we want to bring to your attention the translation of the article by Julian Templeman from the site O'Reilly with a small announcement of the capabilities of the standard standard C ++ library library.
')
Happy New Year, everyone!

C ++ 17 is a major new release with over 100 new features and significant changes. If we talk about major changes, then in the new version nothing comparable in importance with the rvalue links that we received in C ++ 11 appeared, however, there are a lot of changes and additions, for example, structured bindings and new container types. Moreover, a lot of work has been done to make the whole C ++ language more consistent, the developers have tried to remove useless and unnecessary behaviors from it - for example, trigraph support and std::auto_ptr .

In this article, we will discuss two of the most important innovations of C ++ 17, without which the developer can’t do without creating modern C ++ code. It will be about structured bindings that provide a convenient new way of working with structured types, as well as some new types and containers that have been added to the Standard Library.

Structured bindings for multiple assignments


Structured bindings are a completely new phenomenon, and at the same time very useful. They provide multiple assignments from structured types (for example, tuples, arrays, and structures) —for example, assigning all members of a structure to individual variables in a single assignment statement. So the code turns out more compact and more clear.
Code samples with structural bindings are run on Linux using the clang ++ version 4 compiler with the -std=c++1z flag, which activates C ++ 17 features.

In C ++ 11, tuples appeared that are similar to arrays in that both of them are collections of fixed length, but may contain a mixture of different types. With a tuple, you can return more than one value from a function, like this:

 #include <tuple> auto get() { return std::make_tuple("fred", 42); } 

This simple code returns a two-element tuple, and, starting with the C ++ 14 standard, you can use auto return types for this function, so the declaration of this function is much cleaner than otherwise. Calling a function is simple, but retrieving values ​​from a tuple may look rather clumsy and illogical, and you may need std::get :

 auto t = get(); std::cout << std::get<0>(t) << std::endl; 

You can also use std::tie to bind tuple members to variables that you first want to declare:

 std::string name; int age; std::tie(name, age) = get(); 

However, working with structured bindings in C ++ 17, you can bind members of tuples directly to named variables, and then there is no need for std::get , or you can first declare the variables:

 auto [name, age] = get(); std::cout << name << " is " << age << std::endl; 

By working in this way, we can also get references to the members of the tuple, and this was not possible when using std::tie . Here we get links to the members of the tuple and, when we change the value of one of them, the value of the whole tuple changes:

 auto t2 = std::make_tuple(10, 20); auto& [first, second] = t2; first += 1; std::cout << "value is now " << std::get<0>(t2) << std::endl; 

The output will show that the value of t2 has changed from 10 to 11.

Structured bindings for arrays and structures


The tuple case is most obvious, but structured bindings can also be used with arrays and structures, for example:

 struct Person { std::string name; uint32_t age; std::string city; }; Person p1{"bill", 60, "New York"}; auto [name, age, city] = p1; std::cout << name << "(" << age << ") lives in " << city << std::endl; 

With arrays in the same way:

 std::array<int32_t, 6> arr{10, 11, 12, 13, 14, 15}; auto [i, j, k, l, _dummy1, _dummy2] = arr; 

In this implementation, there are a couple of flaws:

Firstly - and this flaw is also relevant for std::tie - you have to bind all the elements. Therefore, it is impossible, for example, to extract only the first four elements from an array. If you want to partially extract the structure or array, simply substitute stub variables for those members that you do not need, as shown in the example with the array.
Secondly (and this will disappoint programmers who are accustomed to using such an idea in functional languages, for example, in Scala and Clojure), destructurization acts only one level in depth. Suppose I have a Location member in the Person structure:

 struct Location { std::string city; std::string country; }; struct Person { std::string name; uint32_t age; Location loc; }; 

You can construct Person and Location by using nested initialization:

 Person2 p2{"mike", 50, {"Newcastle", "UK"}}; 

It can be assumed that the binding in this case is useful for access to members, but in practice it turns out that such an operation is invalid:

 auto [n, a, [c1, c2]] = p2; //   

Finally, I note that in this way you can extract members only from those classes in which the data you need is public and non-static. This issue is discussed in more detail in the next article on structured bindings.

New library types and containers


A number of new and useful data types have also been added to the Standard Library in C ++ 17, some of which originated in Boost.
The code in this section was tested in Visual Studio 2017.

Probably the simplest type of std::byte - it represents a single byte. For the representation of bytes, developers traditionally used char (signed or unsigned), but now there is a type that can be not only a character or an integer; however, bytes can be converted to integer and back. The std::byte designed to interact with the data store and does not support arithmetic operations, although it supports bitwise operations.

std :: variant

The concept of "option" may seem familiar to those who dealt with Visual Basic. A variant is a type-safe union, which at a given moment of time contains the value of one of the alternative types (and there can be no references, arrays or 'void' ).

A simple example: let's say there is some data where a person’s age can be represented as an integer or as a string with a date of birth. Such information can be represented using a variant containing an unsigned integer or string. Assigning an integer to a variable, we set the value, and then we can extract it with std::get , like this:

 std::variant<uint32_t, std::string> age; age = 51; auto a = std::get<uint32_t>(age); 

If you try to use a member that is not defined in this way, the program will throw an exception:

 try { std::cout << std::get<std::string>(age) << std::endl; } catch (std::bad_variant_access &ex) { std::cout << "Doesn't contain a string" << std::endl; } 

Why use std::variant , but not the usual union? Basically, because unions are present in the language primarily for compatibility with C and do not work with objects that are not related to POD-types. This, in particular, implies that it’s not so easy to put members into a union with copies of custom copy constructors and destructors. With std::variant there are no such restrictions.

std :: optional

The other type, std::optional , is surprisingly useful and in practice provides capabilities that exist in many functional languages. 'optional' is an object that may or may not contain values; this object is convenient to use as the return value of a function when it cannot return a value; then it serves as an alternative, for example, to a null pointer.

Working with optional , we gain an additional advantage: now the possibility of a function failure is clearly indicated directly in the declaration, and, since we have to extract a value from optional, the probability that we accidentally use a zero value is significantly reduced.

The following example defines a conversion function that attempts to turn a string into an integer. Returning to optional , the function leaves this possibility: an invalid string can be passed, which cannot be converted. The caller uses the value_or function to get the value from optional , and if the function fails, it returns the default value of zero (if the conversion failed).

 #include <experimental/optional> using namespace std::experimental; optional<int> convert(const std::string& s) { try { int res = std::stoi(s); return res; } catch(std::exception&) { return {}; } } int v = convert("123").value_or(0); std::cout << v << std::endl; int v1 = convert("abc").value_or(0); std::cout << v1 << std::endl; 

std :: any

Finally, there is std::any , which provides a type-safe container for a single value of any type (provided that it has a constructor when copying). You can check whether any contains any value and extract this value with std::any_cast , like this:

 #include <experimental/any> using namespace std::experimental; std::vector<any> v { 1, 2.2, false, "hi!" }; auto& t = v[1].type(); //     std::any? if (t == typeid(double)) std::cout << "We have a double" << "\n"; else std::cout << "We have a problem!" << "\n"; std::cout << any_cast<double>(v[1]) << std::endl; 

You can use the type() member to get a type_info object that says what is contained in any . An exact match between the types is required, otherwise the program will throw an exception std::bad_any_cast :

 try { std::cout << any_cast<int>(v[1]) << std::endl; } catch(std::bad_any_cast&) { std::cout << "wrong type" << std::endl; } 

When can this type of data come in handy? The simple answer is in all cases when one could use the pointer void* , but in this case type safety is guaranteed. For example, you may need different representations of the base value: say, represent '5' both as an integer and as a string. Such cases are common in interpreted languages, but can be useful in cases where a representation is required that will not be automatically converted.

In this article, only two new C ++ 17 issues are reviewed, and I recommend that any C ++ specialist also get acquainted with all the other new products.

Major compilers, including GCC, Clang and MSVC, already support many of these innovations; More on this here .

There are some very good summarizing articles on the Internet with descriptions of various innovations that appeared in C ++ 17, among which I would especially note Tony van Erd 's article, a detailed article on StackOverflow and an excellent article by Bartek.

Source: https://habr.com/ru/post/345560/


All Articles