New features in C++
Note: taking into account the actual scenarios in OI, this article will not fully study the grammar, only the part that could be applied.
The syntax of this article refers to the C++11 standard. Those with different semantics will use C++11 as the standard. The syntax of C++14 and C++17 might be mentioned if appropriate and will be specially marked.
auto
type specifier¶
The auto
type specifier is used to automatically deduce the types like variables. E.g:
auto a = 1; // a is integer type
auto b = a + 0.1; // b is double type
for
loop based on range¶
Below is the syntax of a range-based for
loop before C++20:
for (range_declaration : range_expression) loop_statement
The code generated by the above grammar equals to the following code (__range
, __begin
and __end
are for illustration only):
auto&& __range = range_expression;
for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
range_declaration¶
A range declaration is a declaration of a named variable whose type is the type, or the reference to the type of the element of the sequence represented by the range expression. The auto
specifier is usually used for automatic type inference.
range_expression¶
A range expression is any expression that can represent a suitable sequence (an array, or an object that defines begin
and end
member functions or free functions), or a list of curly brace initializers. For this reason, we should not modify the range expression in the loop body to invalidate any "iterators" (including "postfix iterators") that have not been traversed.
Here we have an example:
for (int i : {1, 1, 4, 5, 1, 4}) std::cout << i;
loop_statement¶
The loop statement can be any statement. It is usually a compound statement that is the loop body.
Here we have an example:
#include <iostream>
struct C {
int a, b, c, d;
C(int a = 0, int b = 0, int c = 0, int d = 0) : a(a), b(b), c(c), d(d) {}
};
int* begin(C& p) { return &p.a; }
int* end(C& p) { return &p.d + 1; }
int main() {
C n = C(1, 9, 2, 6);
for (auto i : n) std::cout << i << " ";
std::cout << std::endl;
// the loop below is equivalent to the loop above
auto&& __range = n;
for (auto __begin = begin(n), __end = end(n); __begin != __end; ++__begin) {
auto ind = *__begin;
std::cout << ind << " ";
}
std::cout << std::endl;
return 0;
}
Lambda expression¶
Lambda expression is an unnamed function object that can capture variables in the scope. We can understand it as an anonymous inline function. The following is the syntax of Lambda expressions:
[capture] (parameters) mutable -> return-type {statement}
capture clause¶
Lambda expressions begin with a capture clause, which specifies which variables are captured and whether the capture is by value or by reference: variables prefixed with the &
symbol are accessed by reference, and variables without the prefix are accessed by value. The empty capture clause []
tells the body of the lambda expression not to access the variables in the enclosing scope.
We can also use the default capture mode: &
means all captured variables are accessed by reference, and =
means all captured variables are accessed by value. We can then specify the opposite mode for a specific variable explicitly.
For example, if the Lambda body wants to access the external variable a
by reference and access the external variable b
by value, the following clauses are equivalent:
[&a, b]
[b, &a]
[&, b]
[b, &]
[=, &a]
[&a, =]
By default, the variables mentioned in Lambda will be captured.
parameters¶
In most cases, it is similar to the parameter list of a function, for example:
auto lam = [](int a, int b) { return a + b; };
std::cout << lam(1, 9) << " " << lam(2, 6) << std::endl;
In C++14, if the parameter type is generic, you can use auto
to declare the type:
auto lam = [](auto a, auto b)
An example:
int x[] = {5, 1, 7, 6, 1, 4, 2};
std::sort(x, x + 7, [](int a, int b) { return (a > b); });
for (auto i : x) std::cout << i << " ";
This will print the result after sorting the x
array descendingly.
mutable¶
Using variable specifications, the body of the Lambda expression can modify the variables captured by value. If this keyword is used, parameters cannot be omitted (even if it is empty).
return-type¶
If the body of the Lambda only contains a return
statement or does not return a value, this part can be omitted. If the body of the Lambda expression contains a return
statement, the return type will be automatically deduced, and the return type will follow the parameters (unless you want to specify one). Otherwise, the compiler will infer the return type as void
.
For example, the above lam
can also be written as:
auto lam = [](int a, int b) -> int
Here we have two more examples:
auto x1 = [](int i) { return i; }; // OK
auto x2 = [] { return {1, 2}; }; // ERROR: the return type is deduced to void
statement Lambda¶
The body of the Lambda can contain any part of the function. Both ordinary functions and the body of the lambda expression can access the following variable types:
- capture variables from enclosed scope
- parameters
- locally declared variables
- when declared in a
class
, capturethis
- any variable with static storage time, such as global variables
Here we have an example:
#include <iostream>
int main() {
int m = 0, n = 0;
[&, n](int a) mutable { m = (++n) + a; }(4);
std::cout << m << " " << n << std::endl;
return 0;
}
In the end we get the output 5 0
. This is because n
is captured by value, and the original value 0
remains unchanged after calling the Lambda expression. The mutable
specification allows n
to be modified in the main body of the Lambda. If mutable
is deleted, the compilation will fail.
decltype¶
decltype
specifiers can infer the type of the expressions.
#include <iostream>
#include <vector>
int main() {
int a = 1926;
decltype(a) b = a / 2 - 146; // b is integer type
std::vector<decltype(b)> vec = {0}; // vec is std::vector <int> type
std::cout << a << vec[0] << b << std::endl;
return 0;
}
constexpr¶
The constexpr
specifier declares that the value of a function or variable that can be obtained at compile time. The main difference between constexpr
and const
is that constexpr
must be initialized at compile time. The constexpr
specifier used for object declarations has const
, and the constexpr
used for function declarations has inline
. Let's take a look at an example:
int frac(int x) { return x ? x * frac(x - 1) : 1; }
int main() {
constexpr int a = frac(5); // ERROR: function calls must have constant values in constant expressions
return 0;
}
Add constexpr
before int frac(int x)
so that compilation would work.
std::tuple¶
std::tuple
is defined in the header file <tuple>
, which is a heterogeneous container of fixed size (it cannot be changed after the initial element is determined, but there can be any number of initial elements). It is a promotion of std::pair
. Let's take a look at an example:
#include <iostream>
#include <tuple>
#include <vector>
constexpr auto expr = 1 + 1 * 4 - 5 - 1 + 4;
int main() {
std::vector<int> vec = {1, 9, 2, 6, 0};
std::tuple<int, int, std::string, std::vector<int> > tup =
std::make_tuple(817, 114, "514", vec);
std::cout << std::tuple_size<decltype(tup)>::value << std::endl;
for (auto i : std::get<expr>(tup)) std::cout << i << " ";
// std::get<> inside the angle brackets must be an integer constant expression
// the value of the expr constant is 3. Note that the first element number of std::tuple is 0.
// so we std::get a std::vector<int>
return 0;
}
Member functions¶
Function | Usage |
---|---|
operator= |
assign the contents of one tuple to another |
swap |
swap the contents of two tuple |
Example:
constexpr std::tuple<int, int> tup = {1, 2};
std::tuple<int, int> tupA = {2, 3}, tupB;
tupB = tup;
tupB.swap(tupA);
Non-member functions¶
Function | Usage |
---|---|
make_tuple |
create a tuple object whose type is defined according to the type of each argument |
std::get |
tuple access to specified elements |
operator== and more |
lexicographically compare the values in tuple |
std::swap |
specialized std::swap algorithm |
Example:
std::tuple<int, int> tupA = {2, 3}, tupB;
tupB = std::make_tuple(1, 2);
std::swap(tupA, tupB);
std::cout << std::get<1>(tupA) << std::endl;
std::function¶
The class template std::function
is a universal polymorphic function wrapper defined in the header file <functional>
. The instance of std::function
can store, copy and call any callable (Callable) target-function, Lambda expression or other function objects as well as pointers to member functions and data members.
The stored callable object is called the target of std::function
. If std::function
does not contain a target, it is empty. Calling an empty std::function
target will cause a std::bad_function_call
exception to be thrown.
Let's look at an example:
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_ + i << '\n'; }
int num_;
};
void print_num(int i) { std::cout << i << '\n'; }
struct PrintNum {
void operator()(int i) const { std::cout << i << '\n'; }
};
int main() {
// store free function
std::function<void(int)> f_display = print_num;
f_display(-9);
// store Lambda
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
// store the call to member function
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);
// store the call to data member accessor
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';
// store the call to function object
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
}
References¶
build本页面最近更新:,更新历史
edit发现错误?想一起完善? 在 GitHub 上编辑此页!
people本页面贡献者:OI-wiki
copyright本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用