该委员会将基于范围的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
        }
    }
    

  • 人们说,这将使Ranges TS的实现更加容易。能给我一些例子吗?

    最佳答案

    C++ 11/14范围-for过度约束...

    WG21的工作文件是P0184R0,它具有以下动机:



    从发布的Standardese中可以看到,范围的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的Live Example((使用gcc.godbolt.org的assembly)

    上面的operator==StringIterator<>在参数上是对称的,并且不依赖于range-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的Live Example(使用gcc.godbolt.org的assembly,与之前的示例几乎相同)。

    ...并且实际上将支持完全通用的原始“D样式”范围。

    WG21纸N4382有以下建议:



    本质上,这等于D样式范围(这些原语称为emptyfrontpopFront)。仅包含这些原语的定界字符串范围将如下所示:
    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;                }
    };
    

    如果不知道基本范围的基本表示形式,如何从中提取迭代器?如何使它适应可以与range-for一起使用的范围?这是一种方法(另请参阅@EricNiebler的series of blog posts)和@ 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的Live Example(使用gcc.godbolt.org的assembly)

    结论:哨兵不仅仅是将分隔符按入类型系统的一种可爱机制,它们对于support primitive "D-style" ranges(它们本身可能没有迭代器的概念)来说已经足够通用,可以作为新C++ 1z范围的零开销抽象。对于。

    09-10 01:00
    查看更多