Перегрузка std::begin() и std::end() для std::pair

Олег Ляттэ
Олег Ляттэ

Сейчас в стандартной библиотеке есть алгоритмы и функции, которые возвращают диапазон значений в виде пары итераторов. Например алгоритм std::equal_range или одноимённые функции в std::multimap, std::unordered_map и др.

Часто бывает нужно сразу пробежаться по этому диапазону в цикле. Но чтобы это сделать, нужно сохранить полученный диапазон в локальную переменную (имя которой будет видно во всём скоупе, хотя было бы достаточно временной переменной), после чего итерироваться по старинке.

Если же написать простые перегрузки вида

template<typename T>
T std::begin(const std::pair<T, T>& p)
{
    return p.first;
}

template<typename T>
T std::end(const std::pair<T, T>& p)
{
    return p.second;
}

то вместо

std::multimap<std::string, int> m = getMap();
auto range = m.equal_range("hello");
for(auto it = range.first; i != range.second; ++i)
{
    doSomething(it->second);
}

можно написать

std::multimap<std::string, int> m = getMap();
for(const auto& v: m.equal_range("hello"))
{
    doSomething(v.second);
}

Возможно, перегрузки стоит ограничить (например, с помощью enable_if) только для типов, которые удовлетворяют требованиям к итераторам. Однако даже если этого не делать, то использование не итератора там, где ожидается итератор (в том же range for), всё равно приведёт к ошибке компилятора (причём, возможно, более информативной, чем с использованием enable_if в перегрузках begin и end).

0
рейтинг
7 комментариев
yndx-antoshkka
Предложение отличное, но над ним уже работают в Ranges. В open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0459r0.pdf секции [future.iterator_range] есть об этом параграф:

"Algorithms like equal_range and rotate could use a concrete range type instead of pair
as their return type, improving composability. It would also be useful to implement a view::all range view
that yields a lightweight range object that refers to all the elements of a container.

A future paper will propose such a type."
yndx-antoshkka
Victor Dyachenko
Было подобное ещё до принятия C++11. Не каждая пара итераторов является диапазоном, поэтому это просто опасно, делать такое. Например алгоритм, вроде minmax(), но возвращающий итераторы на максимальный и минимальный элементы.
Было ещё предложение сделать тип
template<class Iter>
class range : public std::pair<Iter, Iter>;
Но это предложение тоже отвергли.
Victor Dyachenko
Павел Майоров
Нельзя такое делать для пар, пара элементов имеет другую семантику. Нужен отдельный тип данных. И, кажется, его уже делают.
Павел Майоров
Олег Ляттэ
Согласен с тем, что pair с итераторами - скорее подпорка, чем полноценное воплощение идеи диапазонов (ranges). Однако даже если ("когда", надеюсь) в стандарт и добавят диапазоны, то пойдёт ли комитет на то, чтобы изменить интерфейс уже устоявшихся алгоритмов и контейнеров, и изменит возвращаемый тип с пары на диапазон, поломав тем самым обратную совместимость? Я сомневаюсь. Поэтому и предложил пару простых перегрузок, которые заметно упростят работу в имеющихся на сегодняшний день условиях.
Олег Ляттэ
polukarov.mikhail
У меня была схожая идея, но по поводу std::istream. Можно было бы добавить перегрузку для создания диапазона из потока ввода. Есть правда одна тонкость - std::istream_iterator требует шаблонный параметр указывающий тип читаемого элемента. Ведется ли в комитете обсуждение подобных предложений?
polukarov.mikhail
yndx-antoshkka
polukarov.mikhail, не очень понял вашу идею, объясните поподробнее
yndx-antoshkka
polukarov.mikhail
Сейчас чтобы прочитать что-либо из потока данных необходимо создавать пару итераторов std::istream_iterator<...>. Например:

using namespace std;

istringstream str("0.1 0.2 0.3 0.4");
partial_sum(istream_iterator<double>(str),
istream_iterator<double>(),
ostream_iterator<double>(std::cout, " "));

istringstream buf("1 2 3 4 5 6 7");
copy(istream_iterator<int>(str),
istream_iterator<int>(),
ostream_iterator<int>(cout, " "));

Было бы удобно перегрузить std::begin()/std::end() принимающие std::basic_istream<...>. В таком случае можно будет писать такой код:

istringstream str("0.1 0.2 0.3 0.4");
// using begin()/end()
partial_sum(begin<double>(str), end<double>(str), ostream_iterator<double>(cout, " "));

Ну и в идеале, использовать ranges и производящую функцию типа make_range(), например так:

istringstream buf("1 2 3 4 5 6 7");
// using make_range()
copy(make_range<int>(str), ostream_iterator<int>(cout, " "));


Все выше описанные функции могут быть реализованы примерно так:

template<typename T, typename Elem, typename Traits>
istream_iterator<T, Elem, Traits>
begin(basic_istream<Elem, Traits>& stream) {
return istream_iterator<T, Elem, Traits>(stream);
}

template<typename T, typename Elem, typename Traits>
istream_iterator<T, Elem, Traits>
end(basic_istream<Elem, Traits>&) {
return istream_iterator<T, Elem, Traits>();
}

template<typename T, typename Elem, typename Traits>
range< istream_iterator<T, Elem, Traits> >
make_range(basic_istream<Elem, Traits>& stream) {
return { istream_iterator<T, Elem, Traits>(stream), istream_iterator<T, Elem, Traits>() };
}
polukarov.mikhail
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).