Как и многие люди в наши дни, я пробовал разные функции, которые дает C ++ 11. Один из моих любимых - «петли на основе диапазона».
Я это понимаю:
for(Type& v : a) { ... }
Эквивалентно:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
И это begin()
просто возвращается a.begin()
для стандартных контейнеров.
Но что, если я хочу, чтобы мой пользовательский тип был основан на цикле ?
Должен ли я просто специализироваться begin()
и end()
?
Если мой пользовательский тип принадлежит пространству имен xml
, я должен определить xml::begin()
или std::begin()
?
Короче говоря, каковы руководящие принципы, чтобы сделать это?
c++
for-loop
c++11
customization
ereOn
источник
источник
begin/end
или друга, статического или свободногоbegin/end
. Просто будьте осторожны, в какое пространство имен вы помещаете бесплатную функцию: stackoverflow.com/questions/28242073/…for( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }
. Мне любопытно, как вы обходите тот факт, что `'оператор! = ()` `Трудно определить. А как насчет разыменования (*__begin
) в этом случае? Думаю, было бы здорово, если бы кто-то показал нам, как это делается!Ответы:
Стандарт был изменен с тех пор, как вопрос (и большинство ответов) были опубликованы в решении этого отчета о дефектах .
Способ заставить
for(:)
цикл работать с вашим типомX
теперь один из двух:Создайте член
X::begin()
иX::end()
верните что-то, что действует как итераторСоздать бесплатную функцию
begin(X&)
иend(X&)
что возвратное то , что действует как итератор, в том же пространстве имен вашего типаX
.¹И похоже на
const
вариации. Это будет работать как на компиляторах, которые реализуют изменения отчета о дефектах, так и на компиляторах, которые этого не делают.Возвращаемые объекты не обязательно должны быть итераторами.
for(:)
Петля, в отличие от большей части C ++ стандарта, как указана для расширения к чему - то , эквивалентному :будет выглядеть так:
где переменные, начинающиеся с
__
, предназначены только для экспозиции,begin_expr
иend_expr
это волшебство, которое вызываетbegin
/end
.²Требования к возвращаемому значению начала / конца просты: вы должны предварительно перегрузить
++
, обеспечить правильность выражений инициализации, двоичный файл,!=
который можно использовать в логическом контексте, унарный код ,*
который возвращает то, что вы можете назначить-инициализироватьrange_declaration
, и выставить открытый деструктор.Делать это способом, несовместимым с итератором, вероятно, плохая идея, так как будущие итерации C ++ могут быть относительно коварными в нарушении вашего кода, если вы это сделаете.
Кроме того, вполне вероятно, что в будущем пересмотр стандарта позволит
end_expr
вернуть другой тип, чемbegin_expr
. Это полезно в том смысле, что оно допускает оценку «ленивого конца» (например, обнаружение нулевого завершения), которую легко оптимизировать, чтобы она была такой же эффективной, как рукописный цикл C, и другие подобные преимущества.¹ Обратите внимание, что
for(:)
циклы хранят любой временный объект вauto&&
переменной и передают его вам как lvalue. Вы не можете определить, выполняете ли вы итерацию по временному (или другому значению); такая перегрузка не будет вызванаfor(:)
циклом. См. [Stmt.ranged] 1.2-1.3 из n4527.² Либо вызовите метод
begin
/end
, либо поиск свободной функцииbegin
/ только для ADLend
, либо используйте магию для поддержки массивов в стиле C. Обратите внимание, чтоstd::begin
не вызывается, если неrange_expression
возвращает объект типа вnamespace std
или зависит от него.В C ++ 17 выражение для диапазона было обновлено
с типами
__begin
и__end
были отделены.Это позволяет конечному итератору не совпадать с типом начала. Типом конечного итератора может быть «часовой», который поддерживается только
!=
с типом начального итератора.Практическим примером того, почему это полезно, является то, что ваш конечный итератор может читать «проверьте ваш,
char*
чтобы увидеть, если он указывает'0'
», когда==
сchar*
. Это позволяет C ++ диапазонному выражению генерировать оптимальный код при итерации по нулевому завершающемуchar*
буферу.живой пример в компиляторе без полной поддержки C ++ 17;
for
цикл вручную расширен.источник
begin
иend
функций, чем в обычном коде. Возможно, тогда они могли бы быть очень специализированными, чтобы вести себя по-другому (то есть быстрее, игнорируя аргумент end, чтобы получить максимально возможную оптимизацию). Но я недостаточно хорош с пространствами имен, чтобы быть уверенным, как это сделать.begin(X&&)
. Временная задержка в воздухеauto&&
в зависимости от диапазона иbegin
всегда вызывается с lvalue (__range
).Я пишу свой ответ, потому что некоторые люди могут быть более довольны простым примером из жизни без включенных STL.
По какой-то причине у меня есть собственная реализация массива данных только для чтения, и я хотел использовать диапазон, основанный на цикле. Вот мое решение:
Тогда пример использования:
источник
const
квалификатор возврата дляconst DataType& operator*()
и позволить пользователю выбрать использованиеconst auto&
илиauto&
? В любом случае спасибо, отличный ответ;)Соответствующая часть стандарта - 6.5.4 / 1:
Итак, вы можете сделать любое из следующего:
begin
иend
функции-членыbegin
иend
освободить функции, которые будут найдены ADL (упрощенная версия: поместите их в то же пространство имен, что и класс)std::begin
иstd::end
std::begin
вbegin()
любом случае вызывает функцию-член, поэтому, если вы реализуете только одно из вышеперечисленных, результаты должны быть одинаковыми, независимо от того, какой вы выберете. Это те же результаты для циклических циклов for, а также тот же результат для простого смертного кода, который не имеет своих собственных правил разрешения магических имен, поэтому простоusing std::begin;
следует неквалифицированный вызовbegin(a)
.При реализации функции - члены и функции ADL, хотя, то диапазон на основе для петель должны вызывать функции - члены, в то время как простые смертные будут вызывать функции ADL. Лучше убедитесь, что они делают то же самое в этом случае!
Если вещь, которую вы пишете, реализует интерфейс контейнера, то он будет иметь
begin()
иend()
функции-члены, которых должно быть достаточно. Если это диапазон, который не является контейнером (что было бы неплохо, если бы он был неизменным или если вы не знаете размер заранее), вы можете выбирать.Из вариантов, которые вы выкладываете, обратите внимание, что вы не должны перегружаться
std::begin()
. Вам разрешено специализировать стандартные шаблоны для определенного пользователем типа, но помимо этого добавление определений в пространство имен std является неопределенным поведением. Но в любом случае, специализация стандартных функций - плохой выбор хотя бы потому, что отсутствие частичной специализации функций означает, что вы можете сделать это только для одного класса, а не для шаблона класса.источник
!=
, префикс++
и унарный код*
. Вероятно, неразумно реализовыватьbegin()
иend()
функции-члены или функции, не являющиеся членами ADL, которые возвращают что-либо кроме итератора, но я думаю, что это законно.std::begin
Я думаю, что специализироваться на возвращении не итератора - UB.Насколько я знаю, этого достаточно. Вы также должны убедиться, что увеличение указателя дойдет от начала до конца.
Следующий пример (отсутствует константная версия начала и конца) компилируется и работает нормально.
Вот еще один пример с началом / концом в качестве функции. Они должны быть в том же пространстве имен, что и класс, из-за ADL:
источник
return v + 10
.&v[10]
разыменовывает ячейку памяти сразу за массивом.В случае, если вы хотите поддержать итерацию класса непосредственно его
std::vector
или егоstd::map
членом, вот код для этого:источник
const_iterator
также можно получить вauto
(C ++ 11) -соместимым способом черезcbegin
,cend
и т.д.Здесь я делюсь простейшим примером создания пользовательского типа, который будет работать с « основанным на диапазоне для цикла »:
Надеюсь, это будет полезно для такого начинающего разработчика, как я: p :)
Спасибо.
источник
end()
Сама функция , очевидно , не разыменовывает неподходящее место памяти, так как он принимает только «Адреса» этой ячейка памяти. Добавление дополнительного элемента будет означать, что вам потребуется больше памяти, и использованиеyour_iterator::end()
любого способа, который будет разыменовывать это значение, не будет работать с любыми другими итераторами, так как они построены одинаково.return &data[sizeofarray]
IMHO, он должен просто возвращать адресные данные + sizeofarray, но что я знаю,data + sizeofarray
был бы правильный способ написать это.Ответ Криса Редфорда также работает для контейнеров Qt (конечно). Вот адаптация (обратите внимание, я возвращаю
constBegin()
, соответственно,constEnd()
из методов const_iterator):источник
Я хотел бы разработать некоторые части ответа @Steve Jessop, для которых я сначала не понял. Надеюсь, поможет.
https://en.cppreference.com/w/cpp/language/range-for :
Для цикла на основе диапазона функции-члены выбираются первыми.
Но для
Функции ADL выбираются первыми.
Пример:
источник