⬆️ ⬇️

Overview of the new features of C ++ 14: Part 1

In April, a meeting of the C ++ committee was held in Bristol, at which the first proposals for amending the new C ++ 14 standard were considered. All the changes considered in this article were approved at this meeting and already occupy their place of honor in the latest version of the draft of the new standard (N3690 dated May 15, 2013).



Short list:





Changes in the language itself



Automatic detection of return type for normal functions



Starting from C ++ 11, it became possible in the language to define lambda expressions, for which, if you have only one return statement, you can not specify the type of the return value - the compiler can output it independently. Many were surprised that this feature is not available for normal functions. The corresponding proposal was made before the release of C ++ 11, but it was postponed due to time constraints, and now, several years later, it has been added to the standard. Also, this amendment says that if a function or lambda expression has several return that return the same type, in this case, the compiler must also output the type value automatically, including the case with a recursive call.

 auto iterate(int len) //   - int { for (int i = 0; i < len; ++i) if (search (i)) return i; return -1; } auto h() { return h(); } // ,      auto sum(int i) { if (i == 1) return i; //   - int else return sum(i-1)+i; //     } template <class T> auto f(T t) { return t; } //       []()->auto& { return f(); } //   


Although this opportunity has great potential, it has some limitations:

If you place the function declaration in the header file, and the definition is in the corresponding file with the source code, then when you connect the header file to other files, the compiler will not be able to display the type:

 // foo.h class Foo { public: auto getA() const; }; // foo.cpp #include "foo.h" auto Foo::getA() const { return something; } // main.cpp #include "foo.h" int main() { Foo bar; auto a = bar.getA(); // ,    } 


So it will be possible to use it only with local functions or with functions defined in header files. The latter, as a rule, include template functions - the main, in my opinion, application for this novelty.

Restrictions defined in the standard include: a ban on using with virtual functions and a ban on returning an object of type std::initializer_list .



Generalized initialization of captured lambda variables with support for capture-by-movement



In C ++, lambdas do not support capture-by-move. For example, the following code will not compile:

 #include <memory> #include <iostream> #include <utility> template <class T> void run(T&& runnable) { runnable(); }; int main() { std::unique_ptr<int> result(new int{42}); run([result](){std::cout << *result << std::endl;}); } 


To move an object inside a lambda function, it was necessary to write some kind of wrapper, like an outdated auto_ptr.

Instead of adding the ability to explicitly capture-by-move, it was decided to add support for generalized initialization of the captured variables. For example,

 [ x { move(x) }, y = transform(y, z), foo, bar, baz ] { ... } 


In this case, x will be directly initialized by moving x , y will be initialized by the result of the call transform , and the rest will be captured by value. Another example:

 int x = 4; auto y = [&r = x, x = x+1]()->int { r += 2; return x+2; }(); //  ::x  6,   y 7-. 


Explicit indication of the type of initialized captured variables is not prohibited:

 [int x = get_x()] { ... } [Container y{get_container()}] { ... } [int z = 5] { ... } 


Many may wonder why they simply do not support the following method:

 [&&x] { ... } 


The problem is that we do not capture by the rvalue link, we are trying to move. If we went this way, the movement could have happened much later, when the real object may no longer exist. We have to move the object during the capture of variables, and not during the call to the lambda.

')

Generalized (polymorphic) lambda expressions



In C ++ 11, lambda expressions create objects of a class that has a non-generic function call operator. This amendment proposes:



Thus, the same lambda template can be used in different contexts:

 void f1(int (*)(int)) { } void f2(char (*)(int)) { } void g(int (*)(int)) { } // #1 void g(char (*)(char)) { } // #2 void h(int (*)(int)) { } // #3 void h(char (*)(int)) { } // #4 auto glambda = [](auto a) { return a; }; f1(glambda); // OK f2(glambda); // : ID   g(glambda); // :  h(glambda); // OK:  #3,       ID int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK 


Do not forget about the possibility of using, together with universal links and templates, variable number of arguments (variadic templates) :

 auto vglambda = [](auto printer) { return [=](auto&& ... ts) { // OK: ts -     printer(std::forward<decltype(ts)>(ts)...); }; }; auto p = vglambda( [](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; } ); p(1, 'a', 3.14); // OK:  1a3.14 




Simplified restrictions on creating constexpr functions



Changes made allow using constexpr functions:



 constexpr int abs(int x) { if (x < 0) x = -x; return x; // OK } constexpr int first(int n) { static int value = n; // :   return value; } constexpr int uninit() { int a; // :   return a; } constexpr int prev(int x) { return --x; } // OK constexpr int g(int x, int n) { // OK int r = 1; while (--n > 0) r *= x; return r; } 


In addition, the rule by which constexpr non-static member functions implicitly obtained the const specifier was removed (for more details, click here ).



Variable patterns



This feature allows you to create and use constexpr variable templates for more convenient combination with template algorithms (it is possible to use not only with built-in types, but also with user-defined types):

 template<typename T> constexpr T pi = T(3.1415926535897932385); template<typename T> T circular_area(T r) { return pi<T> * r * r; } struct matrix_constants { template<typename T> using pauli = hermitian_matrix<T, 2>; template<typename T> constexpr pauli<T> sigma1 = { { 0, 1 }, { 1, 0 } }; template<typename T> constexpr pauli<T> sigma2 = { { 0, -1i }, { 1i, 0 } }; template<typename T> constexpr pauli<T> sigma3 = { { 1, 0 }, { -1, 0 } }; }; 




Changes in the standard library



exchange



Atomic (atomic) objects provide the atomic_exchange function, which allows you to assign a new value to the object and return its old value. This function can be useful for ordinary objects.

 //   template<typename T, typename U=T> T exchange(T& obj, U&& new_val) { T old_val = std::move(obj); obj = std::forward<U>(new_val); return old_val; } 


For priative types, this function does the same thing as the regular implementation. For complex types, this function allows you to:



For example, the implementation of std::unique_ptr::reset :

 template<typename T, typename D> void unique_ptr<T, D>::reset(pointer p = pointer()) { pointer old = ptr_; ptr_ = p; if (old) deleter_(old); } 


can be improved to:

 template<typename T, typename D> void unique_ptr<T, D>::reset(pointer p = pointer()) { if (pointer old = std::exchange(ptr_, p)) deleter_(old); } 




make_unique



In C ++ 11, along with smart pointers, a function such as make_shared . It allows you to optimize the allocation of memory for shared_ptr , as well as to increase security regarding exceptions. Although a similar optimization for unique_ptr not possible, increased security with respect to exceptions never unique_ptr . For example, here, in both cases, we can get a memory leak if an exception is thrown during the creation of one object, and the second object has already been created but not yet placed in unique_ptr :

 void foo(std::unique_ptr<A> a, std::unique_ptr<B> b); int main() { foo(new A, new B); foo(std::unique_ptr<A>{new A}, foo(std::unique_ptr<B>{new B}); } 


To solve this situation, it was decided to add the function make_unique . Thus, C ++ 14 almost completely (with the exception of very rare cases) suggests that programmers abandon the new and delete operators.

Usage example:

 #include <iostream> #include <string> #include <memory> using namespace std; void foo(std::unique_ptr<string> a, std::unique_ptr<string> b) {} int main() { cout << *make_unique<int>() << endl; cout << *make_unique<int>(1729) << endl; cout << "\"" << *make_unique<string>() << "\"" << endl; cout << "\"" << *make_unique<string>("meow") << "\"" << endl; cout << "\"" << *make_unique<string>(6, 'z') << "\"" << endl; auto up = make_unique<int[]>(5); for (int i = 0; i < 5; ++i) { cout << up[i] << " "; } cout << endl; foo(make_unique<string>(), make_unique<string>()); //    auto up1 = make_unique<string[]>("error"); //  auto up2 = make_unique<int[]>(10, 20, 30, 40); //  auto up3 = make_unique<int[5]>(); //  auto up4 = make_unique<int[5]>(11, 22, 33, 44, 55); //  } /* Output: 0 1729 "" "meow" "zzzzzz" 0 0 0 0 0 */ 




Detached lines



When using strings that include a space, with input / output streams, it is not always possible to get the expected results. For example:

 std::stringstream ss; std::string original = "foolish me"; std::string round_trip; ss << original; ss >> round_trip; std::cout << original; // : foolish me std::cout << round_trip; // : foolish assert(original == round_trip); // assert  


This amendment introduces into the standard a function that allows to handle such situations correctly by adding double quotes at the beginning and at the end of a line when writing and deleting them when reading. For example:

 std::stringstream ss; std::string original = "foolish me"; std::string round_trip; ss << quoted(original); ss >> quoted(round_trip); std::cout << original; // : foolish me std::cout << round_trip; // : foolish me assert(original == round_trip); // assert   


If the string already contains quotes in itself, they will also be separated:

 std::cout << "She said \"Hi!\""; // : She said "Hi!" std::cout << quoted("She said \"Hi!\""); // : "She said \"Hi!\"" 


This amendment is based on a boost analog .



Custom literals for standard library types



C ++ 11 introduces the concept of custom literals (PL), but not a single PL was defined for the standard library, although according to the standard, PL names that do not begin with an underscore are reserved for STL.

The corresponding amendment adds the following custom literals to the standard:

For example:

 auto mystring = "hello world"s; //  std::string auto mytime = 42ns; //  chrono::nanoseconds 


User literals s do not conflict, since they accept different types of parameters.



optional



This amendment introduces a new type in the standard library, meaning an optional object. Possible uses:



Approximate method of use:

 optional<int> str2int(string); //     ,   int get_int_form_user() { string s; for (;;) { cin >> s; optional<int> o = str2int(s); // 'o'    ,     if (o) { //   'o'  ? return *o; //   } } } 


This amendment is based on a boost analog .

A translation of the article by the author of this amendment on this class can be found here .



shared_mutex and shared_lock



When developing multi-threaded programs, it sometimes becomes necessary to give multiple objects read access or unique write access to some object. This amendment adds to the standard shared_mutex library designed for this purpose. The lock function provides unique access, and can be used with lock_guard and unique_lock previously added. To get shared access, you need to use the lock_shared function, and it’s better to do this through the shared_lock / RAII class shared_lock :

 using namespace std; shared_mutex rwmutex; { shared_lock<shared_mutex> read_lock(rwmutex); //  } { unique_lock<shared_mutex> write_lock(rwmutex); //  lock_guard //  } 


It is worth noting that the cost of any lock, even for reading, is more expensive than using a regular mutex.

This amendment is based on a boost analog .



dynarray



In the current standard C, there are dynamic arrays whose size can only be determined at run time. Like ordinary arrays, their data is created on the stack. C ++ 14 claims such arrays, but at the same time it also defines its own kind of dynamic array - std::dynarray , which also recognizes its size at run time and cannot change it further, however, it can either place objects on the stack, or place them in a heap, while having an interface in many ways similar to std::array and std::vector . According to the amendment, this class can be explicitly optimized by the compiler, for use cases of the stack.



Conclusion



Basically, C ++ 14 is positioned as a minor bug-fix release, correcting C ++ 11 flaws, due to time constraints or for some other reason. The current draft standard C ++ can be found here .



UPDATE:

dynarray and optional have been moved to separate technical specifications.

Overview of the new features of C ++ 14: Part 2

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



All Articles