Как новый цикл for на основе диапазона в C++17 помогает диапазонам TS?
комитет изменил диапазон на основе цикла for от:
C++11:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
на C++17:
{
auto && __range = range_expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
и люди говорили, что это облегчит реализацию диапазонов TS. Не могли бы вы привести несколько примеров?
2 ответов:
диапазон C++11/14 -
forбыл слишком напряжен...бумага WG21 для этого P0184R0 который имеет следующую мотивацию:
существующий цикл for на основе диапазона является чрезмерно ограниченным. Конец итератор никогда не увеличивается, не уменьшается и не разыменовывается. Требующий быть итератором не служит никакой практической цели.
как вы можете видеть из стандартного текста, который вы разместили,
endитератор диапазона используется только в цикле условие__begin != __end;. Отсюдаendтолько должно быть равенство, сопоставимое сbegin, и для этого не нужно быть dereferenceable или incrementable....что искажает
operator==для итераторов с разделителями.так что недостаток ли это? Хорошо, если у вас есть диапазон с разделителями-часовыми (C-строка, строка текста и т. д.), затем вы должны включить условие цикла в итератор
operator==, по существу, как это#include <iostream> template <char Delim = 0> struct StringIterator { char const* ptr = nullptr; friend auto operator==(StringIterator lhs, StringIterator rhs) { return lhs.ptr ? (rhs.ptr || (*lhs.ptr == Delim)) : (!rhs.ptr || (*rhs.ptr == Delim)); } friend auto operator!=(StringIterator lhs, StringIterator rhs) { return !(lhs == rhs); } auto& operator*() { return *ptr; } auto& operator++() { ++ptr; return *this; } }; template <char Delim = 0> class StringRange { StringIterator<Delim> it; public: StringRange(char const* ptr) : it{ptr} {} auto begin() { return it; } auto end() { return StringIterator<Delim>{}; } }; int main() { // "Hello World", no exclamation mark for (auto const& c : StringRange<'!'>{"Hello World!"}) std::cout << c; }Видео С g++ - std=c++14, (сборка использование gcc.godbolt.org)
выше
operator==наStringIterator<>симметричен в своих аргументах и не зависит от того, является ли диапазон-forbegin != endилиend != begin(в противном случае вы можете обмануть и сократить код пополам).для простых шаблонов итераций компилятор может оптимизировать запутанную логику внутри
operator==. Действительно, для приведенного выше примера,operator==сводится к одному сравнению. Но будет ли это продолжать работать для длинных трубопроводов диапазонов и фильтров? Кто знает. Скорее всего, потребуется героическая оптимизация уровней.C++17 ослабит ограничения, которые упростят разделенные диапазоны...
так где же именно проявляется упрощение? В
operator==, который теперь имеет дополнительные перегрузки, принимая пару итератор / страж (в обоих порядках, для симметрия.) Таким образом, логика времени выполнения становится логикой времени компиляции.#include <iostream> template <char Delim = 0> struct StringSentinel {}; struct StringIterator { char const* ptr = nullptr; template <char Delim> friend auto operator==(StringIterator lhs, StringSentinel<Delim> rhs) { return *lhs.ptr == Delim; } template <char Delim> friend auto operator==(StringSentinel<Delim> lhs, StringIterator rhs) { return rhs == lhs; } template <char Delim> friend auto operator!=(StringIterator lhs, StringSentinel<Delim> rhs) { return !(lhs == rhs); } template <char Delim> friend auto operator!=(StringSentinel<Delim> lhs, StringIterator rhs) { return !(lhs == rhs); } auto& operator*() { return *ptr; } auto& operator++() { ++ptr; return *this; } }; template <char Delim = 0> class StringRange { StringIterator it; public: StringRange(char const* ptr) : it{ptr} {} auto begin() { return it; } auto end() { return StringSentinel<Delim>{}; } }; int main() { // "Hello World", no exclamation mark for (auto const& c : StringRange<'!'>{"Hello World!"}) std::cout << c; }Видео использование g++ - std=c++1z (сборка использование gcc.godbolt.org, который почти идентичен предыдущему примеру).
...и фактически будет поддерживать полностью общие, примитивные диапазоны "D-стиля".
WG21 бумаги N4382 есть следующее предложение:
C. 6 диапазон фасад и адаптер утилиты [будущее.фасад]
1 до становится тривиальным для пользователей создавать свои собственные типы итераторов, полный потенциал итераторов останется нереализованным. Абстракция диапазона делает это достижимым. С правильными компонентами библиотеки, это должно быть возможно для пользователей, чтобы определить диапазон с минимальным интерфейсом (например,,
current,doneиnextчленов), а есть типы iterator автоматически сгенерированный. Такой шаблон класса фасада диапазона остается как будущая работа.по существу, это равно диапазонам D-стиля (где эти примитивы называются
empty,frontиpopFront). Диапазон строк с разделителями только с этими примитивами будет выглядеть примерно так:template <char Delim = 0> class PrimitiveStringRange { char const* ptr; public: PrimitiveStringRange(char const* c) : ptr{c} {} auto& current() { return *ptr; } auto done() const { return *ptr == Delim; } auto next() { ++ptr; } };если вы не знаете базового представления примитивного диапазона, как извлечь из него итераторы? Как адаптировать это к диапазону, который можно использовать с диапазон-
for? Вот один из способов (см. также серия сообщений в блоге by @EricNiebler) и комментарии от @T. C.:#include <iostream> // adapt any primitive range with current/done/next to Iterator/Sentinel pair with begin/end template <class Derived> struct RangeAdaptor : private Derived { using Derived::Derived; struct Sentinel {}; struct Iterator { Derived* rng; friend auto operator==(Iterator it, Sentinel) { return it.rng->done(); } friend auto operator==(Sentinel, Iterator it) { return it.rng->done(); } friend auto operator!=(Iterator lhs, Sentinel rhs) { return !(lhs == rhs); } friend auto operator!=(Sentinel lhs, Iterator rhs) { return !(lhs == rhs); } auto& operator*() { return rng->current(); } auto& operator++() { rng->next(); return *this; } }; auto begin() { return Iterator{this}; } auto end() { return Sentinel{}; } }; int main() { // "Hello World", no exclamation mark for (auto const& c : RangeAdaptor<PrimitiveStringRange<'!'>>{"Hello World!"}) std::cout << c; }Видео использование g++ - std=c++1z (сборка использование gcc.godbolt.org)
вывод: часовые-это не просто симпатичный механизм для нажатия разделителей в системе типов, они достаточно общие, чтобы поддержка примитивных диапазонов "D-style" (которые сами по себе могут не иметь понятия итераторов) как абстракция с нулевыми накладными расходами для нового диапазона c++1z.
новая спецификация позволяет
__beginи__endбыть разного типа, как__endможно сравнить с__beginдля неравенства.__endдаже не нужно быть итератором и может быть предикатом. Вот глупый пример с определением структурыbeginиendчлены, причем последний является предикатом вместо итератора:#include <iostream> #include <string> // a struct to get the first word of a string struct FirstWord { std::string data; // declare a predicate to make ' ' a string ender struct EndOfString { bool operator()(std::string::iterator it) { return (*it) != '' && (*it) != ' '; } }; std::string::iterator begin() { return data.begin(); } EndOfString end() { return EndOfString(); } }; // declare the comparison operator bool operator!=(std::string::iterator it, FirstWord::EndOfString p) { return p(it); } // test int main() { for (auto c : {"Hello World !!!"}) std::cout << c; std::cout << std::endl; // print "Hello World !!!" for (auto c : FirstWord{"Hello World !!!"}) // works with gcc with C++17 enabled std::cout << c; std::cout << std::endl; // print "Hello" }
Comments