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

How the new range-based for loop in C++17 helps Ranges TS?

本文关键字:循环 何帮助 TS Ranges 帮助 for 范围 于范围 c++      更新时间:2023-10-16

委员会将基于范围的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 range- for was overconstrained…

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

现有的基于范围的for循环是过度约束的。结束迭代器永远不会进行自增、自减或解引用操作。要求作为一个迭代器是没有实际用途的。

从你发布的标准中可以看到,范围的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;
}
<<p> 生活例子/strong>用g++化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 (assembly使用gcc.godbolt.org,这几乎与上一个示例相同)。

…并且实际上将完全支持通用的、原始的"D-style"范围。

WG21论文N4382有如下建议:

C.6范围外立面和适配器实用程序[未来]。facade)

1直到它对于用户来说,创建自己的迭代器类型变得微不足道迭代器的潜力仍未实现。范围抽象这是可以实现的。如果有合适的库组件,应该是这样用户可以用最小的接口定义范围(例如:currentdonenext成员),并且具有迭代器类型自动生成。这样的范围facade类模板保留为未来的工作。

本质上,这等于d风格范围(这些原语被称为empty, frontpopFront)。只有这些原语的分隔字符串范围看起来像这样:

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的系列博客文章)和@ 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 (assembly using gcc.godbolt.org)

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

新的规范允许__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) != '' && (*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"
}