πŸ“œ ⬆️ ⬇️

Eight C ++ 17 features that every developer should use

We’ll talk about eight handy changes that affect your everyday code. Four changes concern the language itself, and four more - its standard library.


You may also be interested in the article Ten C ++ 11 Features, which every C ++ developer should use

Thanks


I took some examples from the reports at conferences of the Russian C ++ User Group - for this many thanks to its organizers and speakers! I took examples from:



1. Decomposition at the announcement (structural bindings)



It is convenient to decompose std::pair , std::tuple and structures using the new syntax:


 #include <string> struct BookInfo { std::string title; // In UTF-8 int yearPublished = 0; }; BookInfo readBookInfo(); int main() { //      title  year,     auto [title, year] = readBookInfo(); } 

In C ++ 17, there are restrictions on decomposition when declaring:



Decomposition in the declaration, in principle, can decompose any class - it is enough to write a hint once by specializing tuple_element , tuple_size and get . Read more in the article Adding C ++ 17 structured bindings support to your classes (blog.tartanllama.xyz)


Decomposition during declaration works well in std::map<> and std::unordered_map<> containers with the old .insert() method and two new methods:



An example of a decomposition with try_emplace and a key-value decomposition when traversing a map:


 #include <string> #include <map> #include <cassert> #include <iostream> int main() { std::map<std::string, std::string> map; auto [iterator1, succeed1] = map.try_emplace("key", "abc"); auto [iterator2, succeed2] = map.try_emplace("key", "cde"); auto [iterator3, succeed3] = map.try_emplace("another_key", "cde"); assert(succeed1); assert(!succeed2); assert(succeed3); //    key  value   range-based for for (auto&& [key, value] : map) { std::cout << key << ": " << value << "\n"; } } 

2. Automatic output of template parameters


Key rules:



You can create your own hints for automatic display of template parameters: see Automatic_deduction_guides


An interesting feature: the constructor from initializer_list<> skipped for a list of one element. For some JSON libraries (such as json_spirit) this can be fatal. Do not play with recursive types and STL containers!


 #include <vector> #include <type_traits> #include <cassert> int main() { std::vector v{std::vector{1, 2}}; //  vector<int>,   vector<vector<int>> static_assert(std::is_same_v<std::vector<int>, decltype(v)>); //    assert(v.size() == 2); } 

3. Declaring nested namespaces


Avoid nesting of namespaces, and if not avoided, then declare them like this:


 namespace product::account::details { // ...   ... } 

4. Attributes nodiscard, fallthrough, maybe_unused


Key rules:



For more information about attributes, see the article How to use attributes from C ++ 17 . There will be brief excerpts.

In C ++, you have to add break after each case to the switch constructs, and even an experienced developer can easily forget about this. The fallthrough attribute comes to the rescue, which can be pasted to an empty instruction. In fact, the attribute is attached to the case following the empty instruction.


 enum class option { A, B, C }; void choice(option value) { switch (value) { case option::A: // ... case option::B: // warning: unannotated fall-through between // switch labels // ... [[fallthrough]]; case option::C: // no warning // ... break; } } 

To take advantage of the attribute, the warning -Wimplicit-fallthrough should be included in GCC and Clang. After enabling this option, each case that does not have a fallthrough attribute will generate a warning.


In projects with high performance requirements, you can practice avoiding the emission of exceptions (at least in some components). In such cases, an operation execution error is reported by the return code returned from the function. However, it is very easy to forget to check this code.


 [[nodiscard]] std::unique_ptr<Bitmap> LoadArrowBitmap() { /* ... */ } void foo() { // warning: ignoring return value of function declared // with warn_unused_result attribute LoadArrowBitmap(); } 

If you use, for example, your own class of errors, you can specify the attribute once in its declaration.


 class [[nodiscard]] error_code { /* ... */ }; error_code bar(); void foo() { // warning: ignoring return value of function declared // with warn_unused_result attribute bar(); } 

Sometimes programmers create a variable that is used only in the debug version to store the error code of the called function. Perhaps this is just a code design error, and the return value should always be processed. However:


 // !  ! auto result = DoSystemCall(); (void)result; //    unused variable assert(result >= 0); //   [[maybe_unused]] auto result = DoSystemCall(); assert(result >= 0); 

5. Class string_view for string parameters


Rules:



For more information on why string_view is best applied only to parameters, see std :: string_view is constructed from temporary instances of strings.

The string_view class string_view good because it is easily constructed from both std::string and const char* without additional memory allocation. It also has constexpr support and repeats the std :: string interface. But there is a minus: for string_view presence of a null character at the end is not guaranteed.


6. Classes optional and variant


The use of optional<> and variant<> so wide that I will not even try to fully describe them in this article. Key rules:



Sample code with optional:


 // nullopt -     nullopt_t,   //  optional ( nullptr  ) std::optional<int> optValue = std::nullopt; // ...  optValue ... //   ,  -1 const int valueOrFallback = optValue.value_or(-1); 


Example code with variant: here we use variant to store one of several states in the case when different states may have different data


 struct AnonymousUserState { }; struct TrialUserState { std::string userId; std::string username; }; struct SubscribedUserState { std::string userId; std::string username; Timestamp expirationDate; LicenseType licenceType; }; using UserState = std::variant< AnonymousUserState, TrialUserState, SubscribedUserState >; 

The variant advantage in its approach to memory management: data is stored in fields of type variant type without additional allocations of memory. This makes the size of the variant type dependent on the types that make it up. So a size table on 32-bit processors may look like (but this is not accurate):


Illustration


7. Use the functions std :: size, std :: data, std :: begin, std :: end



It may be better to rely on the GSL library (C ++ Core Guidelines Support Library) for byte manipulations.


8. Use std :: filesystem


Key rules:



What is bad boost :: filesystem? It turns out that he has several design problems:



Any experienced programmer knows the difference in processing paths between Windows and UNIX systems:



Of course, filesystem abstracts from such differences and makes it easy to work with both platform-specific strings and universal UTF-8:



Bonus rule: stop reinventing clamp, int_to_string and string_to_int


The std :: clamp function complements the min and max functions. It cuts off the meaning from above and below. A similar function boost::clamp is available in earlier versions of C ++.


The rule "do not reinvent the clamp" can be summarized: in any large project, avoid duplicating small functions and expressions for rounding, clipping values, etc. - just add it to your library once.


A similar rule works for string processing tasks. Do you have your own little library for strings and parsing? Does it have parsing or number formatting? If so, replace your implementation with calls to_chars and from_chars


The to_chars and from_chars support error handling. They return two values:



Since in the application code, the error response method may differ, you should place the to_chars and from_chars calls inside your libraries and utility classes.


 #include <utility> //    ,     0 // (   atoi,      ) template<class T> T atoi_17(std::string_view str) { T res{}; std::from_chars(str.data(), str.data() + str.size(), res); return res; } 

')

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


All Articles