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

委员会将基于范围的 for 循环从:

  • C + + 11:

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

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

And people said that this will make implementing Ranges TS easier. Can you give me some examples?

12474 次浏览

新的规范允许 __begin__end是不同的类型,只要 __end可以比较 __begin的不平等。__end甚至不需要是迭代器,可以是谓词。下面是一个愚蠢的例子,它使用 struct 定义 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"
}

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

21国工作组关于这方面的文件是 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;
}

示例 和 g + +-std = c + + 14,(组装使用 gcc.Godbolt.org)

上面的 operator== for 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 (组装使用 gcc.Godbolt.org,几乎与前面的示例相同)。

实际上将支持全面的,原始的“ D 风格”范围。

工作组第21次会议的文件 N4382 提出以下建议:

C. 6 Range Facade 和适配器实用程序[ 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;                }
};

如果你不知道一个基本范围的基底形式,那么如何从中提取迭代器呢?如何适应这一范围,可用于范围-for?这里有一种方法(参见@EricNiebler 的 < strong > 系列博客文章 )和@T 的注释。答:

#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)

结论 : 哨兵不仅仅是一种将分隔符压入类型系统的可爱机制,它们对于 < strong > 支持基本的“ D 样式”范围 (它们自己可能没有迭代器的概念)来说是足够通用的,作为新的 C + + 1z range-for 的零开销抽象。