Подтвердить что ты не робот

Std:: initializer_list без cbegin()/cend()

Если элементы в std::initializer_list всегда являются константными значениями, почему у нас есть шаблонный метод, например begin()/end(), а не cbegin()/cend()? Эти имена (по соглашениям, по сравнению с, например, std::vector) могут предполагать, что оба метода std::initializer_list могут возвращать iterator, когда они всегда возвращают const_iterator.

4b9b3361

Ответ 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
    };

    // ...
}