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


71

该委员会将基于范围的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的实施更加容易。能给我一些例子吗?


6
我可以看到的唯一区别是1.实现要求__begin和__end具有相同的类型。不需要第二实施。
米哈尔Walenciak

7
是。该提案本身在动机中指出:现有的基于范围的for循环过于受限。最终迭代器永远不会递增,递减或取消引用。要求它是迭代器没有实际目的。放宽基于范围的for循环的类型要求,可以使Ranges TS的用户获得最佳体验。我想知道最佳体验是什么样的。 open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0184r0.html
Dimitar Mirchev

1
我认为主要是为了支持代理端迭代器。
红色XIII

1
范围允许将前哨作为结束标记(例如,以空终止的字符串结尾),如果__begin和__end都是迭代器,则不可能实现。
Mr.WorshipMe '18

Answers:


54

C ++ 11/14范围-过于for受限...

为此,WG21论文为P0184R0,其动机如下:

现有的基于范围的for循环受到过度约束。最终迭代器永远不会递增,递减或取消引用。要求它是迭代器没有实际目的。

从发布的Standardese中可以看到,end范围的迭代器仅在loop-condition中使用__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==forStringIterator<>参数是对称的,并且不取决于range-for是begin != endor 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范围外观和适配器实用程序[future.facade]

1在用户创建自己的迭代器类型变得很简单之前,迭代器的全部潜力都将无法实现。范围抽象使这成为可能。有了合适的库组件,它应该是用户能够定义与最少的接口(例如,范围 currentdonenext成员),和具有的迭代器类型自动生成。这样的范围外观类模板将留作将来的工作。

从本质上讲,这是等于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的系列博客文章)和@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;
}

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

结论:哨兵不仅仅是将定界符压入类型系统的一种可爱机制,它们还通用到足以支持原始的“ D样式”范围(它们本身可能没有迭代器的概念)作为新C的零开销抽象。 ++ 1z范围。


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

是。很好的例子,谢谢。但是我试图找到Ranges TS的特定示例。
Dimitar Mirchev

4
@DimitarMirchev:范围TS实际上没有定义任何范围。它定义了许多作用于范围的算法,以及Concepts TS概念,允许人们编写使用范围的代码。但是Range TS v1没有提供任何实际的范围类型。因此,没有可以提供的示例。
Nicol Bolas

@NicolBolas为什么与Ranges TS相关?我认为这是因为Ranges TS支持这些不对称的迭代器/前哨范围。
Yakk-Adam Nevraumont

1
@Yakk:是的,Range TS定义了允许迭代器/前哨配对的范围概念。但是它没有定义使用它们的任何实际范围。因此,Range TS唯一能显示的就是一个概念。简单地说“迭代器/前哨配对就可以了”,这是我们已经知道的。它没有显示其用法的示例。
Nicol Bolas's

2
我在n4128了一个例子。另请参见有关标记和代码生成附录
埃里克·尼布勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.