Вопрос по c++, for-loop, c++11, c++17 – Как новый цикл на основе диапазона в C ++ 17 помогает Ranges TS?

63

Комитет изменил диапазон для цикла:

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 
    } 
}

И люди говорили, что это облегчит реализацию Ranges TS. Можете ли вы дать мне несколько примеров?

В основном для поддержки итераторов конца прокси я полагаю. Red XIII
Диапазоны допускают использование часовых в качестве маркеров конца (например, для строк с нулевым символом в конце), это было невозможно, если и __begin, и __end были итераторами. Mr.WorshipMe
Единственное отличие, которое я вижу, состоит в том, что 1. реализация требует, чтобы __begin и __end были одного типа. Нет такой необходимости во второй реализации. Michał Walenciak
Да. Само предложение гласит: «Мотивация: существующий цикл for на основе диапазона слишком ограничен. Конечный итератор никогда не увеличивается, не уменьшается и не разыменовывается. Требование быть итератором не имеет практической цели. Ослабление требований к типу для цикла for на основе диапазона дает пользователям Ranges TS наилучший возможный опыт. Интересно, как выглядит этот лучший опыт?open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0184r0.html Dimitar Mirchev

Ваш Ответ

2   ответа
46
C ++ 11/14 Range-for был переутомлен ...

P0184R0 которая имеет следующую мотивацию:

Существующий диапазон для цикла for чрезмерно ограничен. Конечный итератор никогда не увеличивается, не уменьшается и не разыменовывается. Требование быть итератором не имеет практической цели.

Как вы можете видеть из опубликованных вами стандартов,end итератор диапазона используется только в условии цикла__begin != __end;, следовательноend только должно быть равенство сравнимо сbeginи он не должен быть разыменованным или увеличиваемым.

... что искажает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<> является симметричным в своих аргументах и ​​не зависит от того, является ли диапазон дляbegin != 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-стиля".

Бумага WG21N4382 имеет следующее предложение:

Утилиты фасадов и адаптеров C.6 [future.facade]

1 Пока пользователям не станет тривиально создавать свои собственные типы итераторов, весь потенциал итераторов останется нереализованным. Абстракция диапазона делает это достижимым. С правильными компонентами библиотеки пользователи должны иметь возможность определять диапазон с минимальным интерфейсом (например,current, done, а такжеnext членов) и автоматически генерировать типы итераторов. Такой шаблон класса фасада диапазона оставлен как будущая работа.

По сути, это равно диапазонам в стиле 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? Вот один из способов (см. Такжесерия постов в блоге @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.

38

Новая спецификация позволяет__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) != '\0' && (*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"
}
Да. Это хороший пример, спасибо. Но я пытался найти конкретный пример для Ranges TS. Dimitar Mirchev
Я привел пример вn4128, Также см.приложение по сторожам и генерации кода. Eric Niebler
@NicolBolas Почему это связано с диапазоном TS? Я думаю, что это потому, что диапазоны TSопоры эти асимметричные итераторы / дозорные диапазоны. Yakk - Adam Nevraumont
@DimitarMirchev: диапазон TS на самом деле не определяет диапазоны. Он определяет ряд алгоритмов, действующих на диапазоны, и концепции TS, которые позволяют писать код, использующий диапазоны. Но Range TS v1 не предоставляет никаких реальныхтипы диапазонов, Таким образом, нет примеров, которые могут быть предоставлены. Nicol Bolas
@Yakk: Да, Range TS определяет концепции диапазона, которые разрешают спаривание итератора / дозорного. Но это не определяет фактические диапазоны, которые их используют. Таким образом, единственное, что можно показать из Range TS, это концепция. Который просто говорит, что «пары итератор / дозорный в порядке», что мы уже знаем. Это не показывает пример ихиспользование. Nicol Bolas

Похожие вопросы