If elements in std::initializer_list
are always const values, why we have template method like begin()/end()
and not cbegin()/cend()
? This names (by conventions, comparing to e.g. std::vector
) could suggest that both std::initializer_list
method could return iterator
, when they always return const_iterator
.
While I can't provide an insight on the reason why cbegin()
and cend()
are not part of std::initializer_list
's interface in addition to begin()
and end()
, there are certainly good reasons why the last two member functions ought to be there.
One reason is, for instance, that the range-based for
loop is defined by the C++11 Standard precisely in terms of functions begin()
and end()
(Paragraph 6.5.4/1). Therefore, in order to make it possible to use it with initializer lists, std::initializer_list
has to provide the begin()
and end()
member functions:
#include <utility>
#include <iostream>
int main()
{
auto l = { 1, 2, 3, 4, 5 };
for (int x : l) // Works because std::initializer_list provides
// the member functions begin() and end().
{
std::cout << x << " ";
}
}
Moreover, it makes sense to consider that member functions cbegin()
and cend()
were not present before C++11: therefore, having begin()
and end()
on the interface of std::initializer_list
allows making old generic algorithms written in terms of begin()
and end()
work with initializer lists as well, without requiring them to be rewritten.
You write:
These names (by conventions, comparing to e.g.
std::vector
) could suggest that bothstd::initializer_list
method could returniterator
, when they always returnconst_iterator
.
Actually, this analogy is not very appropriate. std::vector
's function begin()
, for instance, returns an iterator
when invoked on a non-const
instance of std::vector
(i.e. a mutable one, whose elements can be modified, added, and removed), and a const_iterator
when invoked on a const
instance (i.e. an immutable one, whose content cannot be altered):
#include <vector>
#include <type_traits>
int main()
{
// A non-const vector...
std::vector<int> v = { 1, 2, 3, 4, 5 };
auto i = v.begin();
static_assert(
std::is_same<decltype(i), decltype(v)::iterator>::value,
// ^^^^^^^^
// ...non-const iterator!
"What?");
// A const vector...
std::vector<int> const vc = { 1, 2, 3, 4, 5 };
auto ic = vc.begin();
static_assert(
std::is_same<decltype(ic), decltype(vc)::const_iterator>::value,
// ^^^^^^^^^^^^^^
// ...const iterator!
"What?");
}
Initializer lists are immutable collections by definition. Per Paragraph 18.9/2 of the C++11 Standard:
An object of type
initializer_list<E>
provides access to an array of objects of typeconst E
. [...]
Since initializer lists are collections of const
elements, the cbegin()
and cend()
functions would actually do the exact same thing that begin()
and end()
do.
In fact, iterator
and const_iterator
are both defined as pointers to constant elements of the initializer list's value type, so it's arguable whether it is the case that begin()
and end()
always return const_iterator
(as you assume), or whether they always return iterator
.
This is how Paragraph 18.9/1 of the C++11 Standard defines the initializer_list
class template:
namespace std {
template<class E> class initializer_list {
public:
typedef E value_type;
// ...
typedef const E* iterator;
typedef const E* const_iterator;
// ...
constexpr const E* begin() const noexcept; // first element
constexpr const E* end() const noexcept; // one past the last element
};
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With