Если элементы в std::initializer_list всегда являются константными значениями, почему у нас есть шаблонный метод, например begin()/end(), а не cbegin()/cend()? Эти имена (по соглашениям, по сравнению с, например, std::vector) могут предполагать, что оба метода std::initializer_list могут возвращать iterator, когда они всегда возвращают const_iterator.
Std:: initializer_list без cbegin()/cend()
Ответ 1
Пока я не могу представить, почему cbegin() и cend() не являются частью интерфейса std::initializer_list в дополнение к begin() и end(), есть, конечно, веские причины, по которым последние два функции-члены должны быть там.
Одной из причин является, например, то, что цикл for на основе диапазона определен стандартом С++ 11 точно в терминах функций begin() и end() (пункт 6.5.4/1). Поэтому, чтобы позволить использовать его с списками инициализаторов, std::initializer_list должен предоставить функции-члены begin() и end():
#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 << " ";
}
}
Кроме того, имеет смысл рассмотреть, что функции-члены cbegin() и cend() не присутствовали до С++ 11: поэтому наличие begin() и end() на интерфейсе std::initializer_list позволяет создавать старые общие алгоритмы, написанные в терминах begin() и end(), также работают с списками инициализаторов, не требуя их перезаписи.
Вы пишете:
Эти имена (по соглашениям, по сравнению с, например,
std::vector) могут предполагать, что оба методаstd::initializer_listмогут возвращатьiterator, когда они всегда возвращаютconst_iterator.
Собственно, эта аналогия не очень уместна. std::vector Функция begin(), например, возвращает iterator при вызове в экземпляре const std::vector (т.е. изменчивом, элементы которого могут быть изменены, добавлены и удалены) и const_iterator при вызове в экземпляре const (т.е. неизменяемом, содержимое которого не может быть изменено):
#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?");
}
Списки инициализаторов по определению являются неизменяемыми коллекциями. В абзаце 18.9/2 стандарта С++ 11:
Объект типа
initializer_list<E>обеспечивает доступ к массиву объектов типаconst E. [...]
Так как списки инициализаторов представляют собой коллекции элементов const, функции cbegin() и cend() фактически выполняют ту же самую вещь, что и begin() и end().
Фактически, iterator и const_iterator определяются как указатели на постоянные элементы типа значения типа инициализатора, поэтому можно утверждать, что в случае begin() и end() всегда возвращаются const_iterator ( как вы предполагаете), или они всегда возвращают iterator.
Вот как параграф 18.9/1 стандарта С++ 11 определяет шаблон класса initializer_list:
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
};
// ...
}