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

标签 c++ c++11 for-loop 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 
       }
    } 
    
  • 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其动机如下:

The existing range-based for loop is over-constrained. The end iterator is never incremented, decremented, or dereferenced. Requiring it to be an iterator serves no practical purpose.

从您发布的标准语可以看出,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;
}

Live Example使用 g++ -std=c++14,(assembly 使用 gcc.godbolt.org)

以上operator==对于 StringIterator<>在其参数中是对称的,并且不依赖于范围是否为 begin != endend != 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;
}

Live Example使用 g++ -std=c++1z(assembly 使用 gcc.godbolt.org,这与前面的示例几乎相同)。

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

WG21 论文 N4382有以下建议:

C.6 Range Facade and Adaptor Utilities [future.facade]

1 Until it becomes trivial for users to create their own iterator types, the full potential of iterators will remain unrealized. The range abstraction makes that achievable. With the right library components, it should be possible for users to define a range with a minimal interface (e.g., current, done, and next members), and have iterator types automatically generated. Such a range facade class template is left as future work.

本质上,这等同于 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 的 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;
}

Live Example使用 g++ -std=c++1z(assembly 使用 gcc.godbolt.org)

结论:sentinel 不仅仅是一种将定界符压入类型系统的可爱机制,它们的通用性足以 support primitive "D-style" ranges (它们本身可能没有迭代器的概念)作为新 C++1z range-for 的零开销抽象。

关于c++ - C++17 中新的基于范围的 for 循环如何帮助 Ranges TS?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41211532/

相关文章:

c++ - 计算分布的逆CDF

c++ - 用于确定距离是否在 `n` 范围内的 STL 函数

c++ - 如何停止执行第一个子进程?

c++ - ThreadSanitizer 检测到数据竞争,问题出在哪里?

windows - Windows批处理文件中的 "for"循环中的随机变量没有改变

.net - 以日期作为迭代器的 For 循环

c++ - 从无符号转换为有符号类型安全?

c++ - 内联 lambda 表达式导致编译器错误

c++ - 如何使用基于范围的循环语法遍历 STL 容器中的连续对?

matlab - 如何在没有for循环的情况下在matlab中计算这个