Подтвердить что ты не робот

Как новый диапазон для цикла в С++ 17 помогает диапазонам TS?

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

  • С++ 11:

    {
       auto && __range = range_expression ; 
       for (auto __begin = begin_expr, __end = end_expr; 
           __begin != __end; ++__begin) { 
           range_declaration = *__begin; 
           loop_statement 
       }
    } 
    
  • в С++ 17:

    {        
        auto && __range = range_expression ; 
        auto __begin = begin_expr ;
        auto __end = end_expr ;
        for ( ; __begin != __end; ++__begin) { 
            range_declaration = *__begin; 
            loop_statement 
        } 
    }
    

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

4b9b3361

Ответ 1

С++ 11/14 range- for был слишком суровым...

Документ WG21 для этого P0184R0, который имеет следующую мотивацию:

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

Как вы можете видеть из опубликованного вами стандартного сообщения, итератор 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;
}

Пример Live с g++ -std = С++ 14, ( сборка с помощью gcc.godbolt.org)

Приведенный выше operator== для StringIterator<> является симметричным по своим аргументам и не зависит от того, является ли диапазон-значение begin != end или end != begin (иначе вы могли бы обмануть и сократить код пополам).

Для простых шаблонов итераций компилятор может оптимизировать запутанную логику внутри operator==. Действительно, для приведенного выше примера operator== сводится к одному сравнению. Но будет ли это продолжаться для длинных трубопроводов диапазонов и фильтров? Кто знает. Вероятно, для этого требуются героические уровни оптимизации.

С++ 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;
}

Пример Live с использованием g++ -std = С++ 1z ( assembly, используя gcc.godbolt.org, который почти идентичен предыдущему примеру).

... и фактически поддерживает полностью общие, примитивные диапазоны D-стиля.

Документ WG21 N4382 имеет следующее предложение:

C.6 Утилиты Facade и Adapter диапазона [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) и комментарии от @TC:

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

Пример Live с помощью g++ -std = С++ 1z ( сборка с помощью gcc.godbolt.org)

Заключение: часовые - это не просто симпатичный механизм нажатия разделителей в систему типов, они достаточно общие для поддержки примитива "D- стиль" диапазонов (которые сами по себе не могут иметь понятия итераторов) как абстракция с нулевым потоком для нового диапазона С++ 1z.

Ответ 2

Новая спецификация позволяет __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"
}