Как новый цикл 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. Не могли бы вы привести несколько примеров?

682   2  

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<> симметричен в своих аргументах и не зависит от того, является ли диапазон-for 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-стиля".

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

    Ничего не найдено.