Since C++11 there are elegant way to access each element of a containers (or, more generally, sequences) – so called range-for-statement. The syntaxes are follow:
1 2 3 4 5 6 7 8 |
template<class Container> void foo(Container& container) { for (auto element : container); // (1) for (const auto element : container); // (2) for (auto& element : container); // (3) for (const auto& element : container); // (4) } |
Semantically, all the above statements means “for each element in container”, where elements of container are traversed in order from container.begin()
till the container.end()
(not inclusive, please see Introduction to C++ Iterators). The expression in place of container
(in the simplest cases such as shown above, this expression is just a reference to the container) must yield an instance of a class with the defined function members begin()
and end()
, or the instance of a class for which the overloads of free functions in the enclosing scope are available to perform calls begin(container)
and end(container)
. In both cases a pair of calls container.begin()
, container.end()
or begin(container)
, end(container)
are used to obtain iterators pointed to the first element and the element one-past-the-last element of the sequence accordingly. Please note, that the auto
could be replaced with a type to which implicit conversion of the type of the elements of the sequence is allowed.
If the syntaxes (1)
and (2)
exposed above are in use, it leads each element of the container
to be copied to the controlled variable element
. While this is fine for cheap to copy values (e.g. values of built-in types, or values of types like std::string_view
), it could be costly for values of large size (e.g. values of type std::string
) which can negatively affect the performance. Thus, be careful and use syntaxes (3)
and (4)
, involving references which are more appropriate for elements that might be large. Please note, that in order to modify an element inside a range-for-statement, the controlled variable element
must be a lvalue reference, and thus, the syntax (3)
have to be used in this case.
Finally, let’s consider the example which demonstrates how to enable range-for-statement for a user-defined class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
#include <iostream> // for demonstration of the usage #include <map> #include <optional> #include <string> namespace foo { class Ide final { public: // ------------------- // Setters and getters // ------------------- void set_name(std::string value) { data_["name"] = std::move(value); } std::optional<std::string> name() const { if (const auto i = data_.find("name"); i != data_.cend()) return i->second; return {}; } void set_version(std::string value) { data_["version"] = std::move(value); } std::optional<std::string> version() const { if (const auto i = data_.find("version"); i != data_.cend()) return i->second; return {}; } // ------------------- // Iterators // ------------------- auto begin() noexcept { return data_.begin(); } auto end() noexcept { return data_.end(); } // For std::cbegin auto begin() const noexcept { return data_.begin(); } // For std::cend auto end() const noexcept { return data_.end(); } auto cbegin() const noexcept { return data_.cbegin(); } auto cend() const noexcept { return data_.cend(); } private: std::map<std::string_view, std::string> data_; }; } // namespace foo // The usage int main() { foo::Ide ide; ide.set_name("Embarcadero RAD Studio"); ide.set_version("10.4"); for (const auto& a : ide) std::cout << "Attribute \"" << a.first << "\" has value: " << a.second << '\n'; } |