a. std::integral – specifies the type should be an integral type
b. std::boolean – specifies the type can be used as a boolean type
c. std::move_constructible – specifies that the object of a particular type can be constructed using the move semantics
d. std::movable – specifies that the object of a certain type T can be moved
e. std::signed_integral – says the type is both integral and is a signed integral
20.3 Lambda Templates
We can now use template syntax in our lambda functions . Example:
auto mylambda = [](T param)
{
// code
};
For example, to printout the generic type name, using a templated lambda expression, we would write:
#include
#include
#include
int main()
{
auto mylambda = [](T param)
{
std::cout << typeid(T).name() << '
';
};
std::vector v = { 1, 2, 3, 4, 5 };
mylambda(v); // integer
std::vector v2 = { 3.14, 123.456, 7.13 };
mylambda(v2); // double
}
20.4 [likely] and [unlikely] Attributes
If we know that some paths of execution are more likely to be executed than others, we can help the compiler optimize the code by placing attributes. We use the [[likely]] attribute before the statement that is more likely to be executed. We can also put the [[unlikely]] attribute before the statement that is unlikely to be executed. For example, the attributes can be used on case branches inside the switch statement:
#include
void mychoice(int i)
{
switch (i)
{
[[likely]] case 1:
std::cout << "Likely to be executed.";
break;
[[unlikely]] case 2:
std::cout << "Unlikely to be executed.";
break;
default:
break;
}
}
int main()
{
mychoice(1);
}
If we want to use these attributes on the if-else branches, we write:
#include
int main()
{
bool choice = true;
if (choice) [[likely]]
{
std::cout << "This statement is likely to be executed.";
}
else [[unlikely]]
{
std::cout << "This statement is unlikely to be executed.";
}
}
20.5 Ranges
A range, in general, is an object that refers to a range of elements. The new C++20 ranges feature is declared inside a header. The ranges themselves are accessed via the std::ranges name. With classic containers such as an std::vector, if we want to sort the data, we would use:
#include
#include
#include
int main()
{
std::vector v = { 1, 2, 3, 4, 5 };
std::sort(v.begin(), v.end());
for (auto el : v)
{
std::cout << el << '
';
}
}
The std::sort function accepts vector’s .begin() and end() iterators. With ranges, it is much simpler, we just provide the name of the range, without iterators:
#include
#include
#include
#include
int main()
{
std::vector v = { 3, 5, 2, 1, 4 };
std::ranges::sort(v);
for (auto el : v)
{
std::cout << el << '
';
}
}
Ranges have a feature called adaptors. One of the range adaptors is views. The views adaptors are accessed via std::ranges::views. Views are not owning. They cannot change the values of the underlying elements. It is also said they are lazily executed. This means the code from the views adaptors will not be executed until we iterate over the result of such views.
Let us create an example which uses range views