begin()
: Iterator to the first elementend()
: Iterator to the position after the last elementrbegin()
: Reverse iterator for reverse iteration (initial position)rend()
: Reverse iterator (position after the last element)cbegin(), cend(), crbegin(), crend()
: Same as above, but iterating over const
elementsinsert(pos, elem)
: Inserts a copy of elem (return value may differ)emplace(pos, args...)
: Inserts an element by constructing it in placeerase(beg, end)
: Removes all elements in the range clear()
: Removes all elements (makes the container empty)C::value_type
: The type of the object stored in a container. value_type
must be assignable and copy constructible, but need not be default constructible.C::iterator
: The type of the iterator used to iterate through a container's elements.C::const_iterator
: A type of iterator that may be used to examine but not modify a container's elements.C::reference
: A type that behaves as a reference to the container's value type.C::const_reference
: A type that behaves as a const reference to the container's value type.C::pointer
: A type that behaves as a pointer to the container's value type.C::difference_type
: A signed integral type used to represent the distance between two of the container's iterators.C::size_type
: An unsigned integral type that can represent any nonnegative value of the container's distance type.Having the type of the contained elements defined in the container may seem peculiar. After all, the type of elements in a vector<T>
is just T
! However, this technique is useful in generic programming:
template <typename Container>
void my_fun(Container &c) {
using ValueType = typename Container::value_type;
// ...
ValueType a;
}
The auto
specifier and decltype()
reduce this need. For instance, you could have written:
using ValueType = decltype(*(c.begin()));
But being explicit is often better! Having traits to specify type members gives a lot of flexibility (and indeed the standard library uses traits...).
The distance between iterators is equal to the number of elements in the range defined by them.
{
const std::set<int> my_set = {10, 20, 30, 40, 50};
auto first = my_set.lower_bound(20); // Iterator to the first element >= 20.
auto second = my_set.lower_bound(40); // Iterator to the first element >= 40.
const int distance = std::distance(first, second); // Calculate the distance.
}
{
const std::vector<int> my_vector = {1, 2, 3, 4, 5};
auto first = my_vector.begin();
auto second = std::find(my_vector.begin(), my_vector.end(), 4); // Return iterator to element 4.
const int distance = std::distance(first, second); // Calculate the distance.
}
Distance may be negative if iterators are random access.
size_type
and std::size_t
Container::size_type
in a sequence container is the type used as an argument in operator[]
, defined for these containers.
It is guaranteed to be an unsigned integral type. Use it instead of int
or unsigned int
if you anticipate problems with implicit conversions. size_type
is implementation-dependent (it may vary between 32-bit and 64-bit architectures).
By default, it is set equal to std::size_t
, defined in <cstddef>
, which is the type used to address ordinary arrays.
If you want to be safe, use std::size_t
or Container::size_type
to address sequential containers.
for (std::size_t i = 0; i < a.size(); ++i)
a[i] = ...;
The term range (or sequence) refers to a pair of iterators that define an interval of elements that are "logically adjacent" within a container.
We provide a working definition. Two iterators b
and e
define a valid range
for (iterator p = b; p != e; ++p) {
*p;
}
is valid, and *p
returns the value of an element of the container.
The algorithms of the standard library typically operate on sequences.
The STL provides an extensive set of algorithms to operate on containers, or more precisely on ranges.
For a full list, you may look here for generic algorithms and here for numeric algorithms.
std::ranges
, with the same name as the old ones, but simpler to use and sometimes more powerful.Many standard algorithms can be implemented using a for loop. So what's the advantage? I start by saying that there is nothing wrong with the for-loop version. If you are happy with it, use it. Yet with standard algorithms:
They Do not modify the value of the range. They work also on constant ranges.
It std::find(ForwardIt first, ForwardIt last, const T & value)
Finds the first occurrence of value
in the range [first, last)
.
They either modify the given range, like
void std::fill(ForwardIt first, ForwardIt last, const T& value);
assigns the given value
to the elements in the range [first, last)
.
Or, they copy the result of an operation into another (existing) range. For instance
OutIt std::copy(InIt first, InIt last, OutIt result);
copies [first, last)
into the range that starts at result
.
Inserters are special iterators used to insert values into a container. Three main types:
std::back_inserter(Container& x)
: Inserts at the back (only for sequential containers).std::front_inserter(Container& x)
: Inserts in the front (only for sequential containers).std::inserter(Container& x, It position)
: Inserts after the indicated position.std::copy(a.begin(), a.end(), std::front_inserter(c));
The computational cost depends on the type of container!
std::inserter
Several algorithms require writing the output to a non-const range indicated by the iterator to its beginning. Without inserters, it would be impossible to use them on a non-sequential container or on a sequential container of insufficient size.
std::vector<double> a;
std::set<double> b;
std::copy(a.begin(), a.end(), b.begin()); // ERROR: b is not large enough.
You need an inserter:
std::copy(a.begin(), a.end(), std::inserter(b, b.begin())); // Ok.
For an associative container, the second argument of inserter
is taken only as a suggestion.
Particular modifying algorithms operating on a range to order it according to an ordering relation (default: std::less<T>
):
std::vector<double> a;
// Descending order: a[i+1] <= a[i].
std::sort(a.begin(), a.end(), std::greater<double>());
// Ascending order: a[i+1] >= a[i].
std::sort(a.begin(), a.end());
bool std::binary_search(It first, It last, const T& value);
returns true if the value
is present.std::set<T>
, it is sufficient that the range is ordered):std::set<int> a;
std::set<int> b;
// ...
set<int> c;
std::set_union(a.begin(), a.end(), b.begin(), b.end(), std::inserter(c, c.begin()));
Now std::set
is already ordered!template <class T>
const T& max(const T& a, const T& b);
template <class T>
const T& min(const T& a, const T& b);
template <class T, class Compare>
const T& max(const T& a, const T& b, Compare comp);
template <class T>
std::pair<const T&, const T&> minmax(const T& a, const T& b);
template <class InputIt1, class InputIt2>
bool lexicographical_compare(InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2);
Numeric operations are available in <numeric>
.
Examples:
std::vector<double> v;
std::vector<double> w;
// Sum of a range.
auto sum = std::accumulate(v.begin(), v.end(), 0);
// Product of a range.
auto product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<double>());
// The same with lambdas.
auto product = std::accumulate(v.begin(), v.end(), 1, [](double a, double b) { return a * b; });
auto r1 = std::inner_product(v.begin(), v.end(), w.begin(), 0);
std::transform
std::transform
, present in two forms:OutIt transform(InIt first1, InIt last1, OutIt result, UnaryOperator op);
OutIt transform(InIt1 first1, InIt1 last1, InIt2 first2, OutIt result, BinaryOperator binary_op);
std::set<double> a;
std::list<double> l;
// ...
std::vector<double> b(a.size());
std::transform(a.begin(), a.end(), l.begin(), b.begin(), std::plus<double>());
std::for_each
: Apply a function to a range.std::find_if
: Find the first element satisfying a predicate.std::count
: Count appearances of a value in a range.std::count_if
: Return the number of elements in a range satisfying a predicate.std::replace
: Replace a value.std::replace_if
: Replace values in a range satisfying a predicate.std::replace_copy
: Copy a range while replacing values.std::replace_copy_if
: Copy a range, replacing values satisfying a predicate.std::fill
: Fill a range with a value.std::fill_n
: Fill n
elements with a value.std::generate
: Generate values according to a given unary function.std::remove_if
: Remove elements satisfying a predicate.std::remove_copy
: Remove values and copy them to another range.std::remove_copy_if
: Remove elements satisfying a predicate and copy.std::unique
: Remove consecutive duplicates.std::random_shuffle
: Rearrange elements in a range randomly.std::partition
: Partition a range into two.Full list here and here for numerical functions and algorithms.
std::execution::seq
: Sequential execution (no parallelization).std::execution::par
: Parallel sequenced execution.std::execution::par_unseq
: Parallel unsequenced execution (vectorization).std::vector<int> v;
// Find element using parallel execution policy.
auto result1 = std::find(std::execution::par, std::begin(v), std::end(v), 2);
// Sort elements using sequential execution policy.
auto result2 = std::sort(std::execution::seq, std::begin(v), std::end(v));
The C++ Standard Template Library (STL) has seen several enhancements and improvements in each major C++ standard release, including C++11, C++14, C++17, C++20, and C++23. Here's a summary of the main introductions to the STL in each of these versions.
for
loops (1/5)for
loop (pre-C++11)std::vector<int> vec = {1, 2, 3, 4, 5};
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
}
std::vector
or std::array
).size()
each time.for
loops (2/5)for
loop with iterators (pre-C++11)std::vector<int> vec = {1, 2, 3, 4, 5};
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::list
).*it
) can make code harder to read.for
loops (3/5)for
loop (C++11)std::vector<int> vec = {1, 2, 3, 4, 5};
for (int value : vec) { // Or: for (const auto value : vec)
std::cout << value << " ";
}
for (int &value : vec)
).for
loops (4/5)std::tuple<int, std::string, double> my_tuple{1, "a string", 2.5};
const auto [i, s, d] = my_tuple; // Unpack tuple.
std::map<int, std::string> my_map = {{1, "one"}, {2, "two"}};
for (const auto& [key, value] : my_map) {
std::cout << key << ": " << value << "\n";
}
std::map
and std::unordered_map
.std::vector
.for
loops (5/5)for
with std::ranges
(C++20)#include <ranges>
for (int value : vec | std::views::reverse) {
std::cout << value << " ";
}
reverse
, filter
, transform
) directly in the loop using range adaptors.The STL is a fundamental part of the C++ standard library, offering a rich set of data structures, algorithms, and utilities that make C++ a powerful and expressive language. To fully harness the power of the STL:
std::vector
, std::map
, std::queue
) based on your specific needs. This decision greatly impacts your code's efficiency.std::shared_ptr
and std::unique_ptr
are crucial for effective memory management, preventing memory leaks and resource leaks.