Стандарт был изменен с тех пор, как вопрос (и большинство ответов) были опубликованы в резолюции об этом дефекте .
Способ заставить цикл for(:)
работать на вашем типе X
теперь является одним из двух способов:
Создать элемент X::begin()
и X::end()
, который возвращает что-то, что действует как итератор
Создайте свободную функцию begin(X&)
и end(X&)
, которая возвращает что-то, действующее как итератор, в том же пространстве имен, что и ваш тип X
.¹
И аналогично для const
вариаций. Это будет работать как на компиляторах, которые реализуют изменения отчета о дефектах, так и на компиляторах, которые этого не делают.
Возвращаемые объекты не обязательно должны быть итераторами. Цикл for(:)
, в отличие от большинства частей стандарта C ++, указан для расширения до значения, эквивалентного :
for( range_declaration : range_expression )
становится:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
где переменные, начинающиеся с __
, предназначены только для экспозиции, а begin_expr
и end_expr
- магия, которая вызывает begin
/ end
.²
Требования к возвращаемому значению начала / конца просты: вы должны перегрузить pre- ++
, убедиться, что выражения инициализации действительны, двоичный !=
, который может использоваться в логическом контексте, унарный *
, который возвращает то, что вы можете назначить, инициализировать range_declaration
и открыть публичный деструктор.
Делать так, чтобы это не было совместимо с итератором, вероятно, плохая идея, так как будущие итерации C ++ могут быть относительно коварными в нарушении вашего кода, если вы это сделаете.
Кроме того, вполне вероятно, что в будущем пересмотр стандарта позволит end_expr
возвращать тип, отличный от begin_expr
. Это полезно тем, что позволяет выполнять «ленивую» оценку (например, обнаружение нулевого завершения), которую легко оптимизировать, чтобы она была такой же эффективной, как рукописный цикл C, и другие подобные преимущества.
¹ Обратите внимание, что циклы for(:)
хранят любые временные значения в переменной auto&&
и передают их вам как lvalue. Вы не можете определить, выполняете ли вы итерацию для временного (или другого значения); такая перегрузка не будет вызвана циклом for(:)
. См. [Stmt.ranged] 1.2-1.3 из n4527.
² Либо вызовите метод begin
/ end
, либо вызов только функции ADL для свободной функции begin
/ end
, или magic для поддержки массивов в стиле C. Обратите внимание, что std::begin
не вызывается, если range_expression
не возвращает объект типа в namespace std
или зависит от него.
В c ++ 17 обновлено выражение для диапазона
{
auto && __range = range_expression ;
auto __begin = begin_expr;
auto __end = end_expr
for (;__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
с типами __begin
и __end
были отделены.
Это позволяет конечному итератору не совпадать с типом начала. Типом конечного итератора может быть «часовой», который поддерживает !=
только с типом начального итератора.
Практический пример того, почему это полезно, заключается в том, что ваш конечный итератор может прочитать «проверьте ваш char*
, чтобы увидеть, указывает ли он на '0'
», когда ==
с char*
. Это позволяет C ++ диапазонному выражению генерировать оптимальный код при итерации по нулевому окончанию буфера char*
.
struct null_sentinal_t {
template<class Rhs,
std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
>
friend bool operator==(Rhs const& ptr, null_sentinal_t) {
return !*ptr;
}
template<class Rhs,
std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
>
friend bool operator!=(Rhs const& ptr, null_sentinal_t) {
return !(ptr==null_sentinal_t{});
}
template<class Lhs,
std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
>
friend bool operator==(null_sentinal_t, Lhs const& ptr) {
return !*ptr;
}
template<class Lhs,
std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
>
friend bool operator!=(null_sentinal_t, Lhs const& ptr) {
return !(null_sentinal_t{}==ptr);
}
friend bool operator==(null_sentinal_t, null_sentinal_t) {
return true;
}
friend bool operator!=(null_sentinal_t, null_sentinal_t) {
return false;
}
};
живой пример в компиляторе без полной поддержки C ++ 17; for
петля расширена вручную.