C++ 17中新的基于范围的for循环如何帮助Ranges TS?

Dim*_*hev 66 c++ for-loop c++11 c++17

委员会改变了基于范围的for循环:

  • C++ 11:

    {
       auto && __range = range_expression ; 
       for (auto __begin = begin_expr, __end = end_expr; 
           __begin != __end; ++__begin) { 
           range_declaration = *__begin; 
           loop_statement 
       }
    } 
    
    Run Code Online (Sandbox Code Playgroud)
  • 到C++ 17:

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

人们说这将使Ranges TS更容易实现.你能举个例子吗?

Tem*_*Rex 51

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

WG21的论文是P0184R0,其动机如下:

现有的基于范围的for循环过度约束.结束迭代器永远不会递增,递减或取消引用.要求它是一个迭代器没有任何实际意义.

从您发布的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;
}
Run Code Online (Sandbox Code Playgroud)

活实施例使用g ++ -std = C++ 14(组件使用gcc.godbolt.org)

以上operator==StringIterator<>是在对称的争论和不依赖于范围内,对是否begin != endend != begin(否则你可以欺骗和剪切代码中的一半).

对于简单的迭代模式,编译器能够优化内部的复杂逻辑operator==.实际上,对于上面的例子,将operator==其简化为单一比较.但这会继续适用于范围和过滤器的长管道吗?谁知道.它可能需要英雄优化级别.

C++ 17将放宽约束,这将简化分隔范围......

那么简化的确切表现在哪里呢?在operator==,现在有额外的重载采用迭代器/ sentinel对(在两个顺序中,对称).因此运行时逻辑变为编译时逻辑.

#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;
}
Run Code Online (Sandbox Code Playgroud)

使用g ++ -std = c ++ 1z的实例(使用gcc.godbolt.org组装,与前面的例子几乎相同).

......并且实际上将支持完全一般的,原始的"D风格"范围.

WG21纸N4382有以下建议:

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;                }
};
Run Code Online (Sandbox Code Playgroud)

如果一个人不知道原始范围的底层表示,如何从中提取迭代器?如何使其适应范围 - 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;
}
Run Code Online (Sandbox Code Playgroud)

活实施例使用的g ++ -std = C++ 1Z(组件使用gcc.godbolt.org)

结论:哨兵不仅仅是将分隔符压入类型系统的可爱机制,它们足以支持原始的"D风格"范围(它们本身可能没有迭代器的概念)作为新C的零开销抽象. ++ 1z range-for.


was*_*ful 39

新规范允许__begin并且__end具有不同类型,只要__end可以与__begin不等式进行比较.__end甚至不需要是迭代器也可以是谓词.这是一个带有结构定义beginend成员的愚蠢示例,后者是谓词而不是迭代器:

#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"
}
Run Code Online (Sandbox Code Playgroud)

  • @DimitarMirchev:范围TS实际上并没有定义任何范围.它定义了许多作用于范围的算法,以及允许用户编写使用范围的代码的概念TS概念.但Range TS v1不提供任何实际*范围类型*.所以没有可以提供的例子. (4认同)
  • 我在[n4128]举了一个例子(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4128.html#an-iterables-end-may-have-a-different型高于其期初).另见[关于哨兵和代码生成的附录](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4128.html#appendix-1-sentinels-and-code-代). (2认同)