委員会は、範囲ベースの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の実装が容易になると言った。例を挙げていただけますか?
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;
}
Live Example g ++ -std = c ++ 14、( Assembly gcc.godbolt.orgを使用)
上記のoperator==
のStringIterator<>
は引数が対称であり、range-forがbegin != end
であるかend != begin
であるかに依存しません(そうでなければ、コードをチートしてカットできます)ハーフ)。
単純な反復パターンの場合、コンパイラはoperator==
内の複雑なロジックを最適化できます。実際、上記の例では、operator==
は単一の比較に削減されます。しかし、これは範囲とフィルターの長いパイプラインで引き続き機能しますか?知るか。英雄的な最適化レベルが必要になる可能性があります。
それでは、単純化はどこで正確に現れますか? 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を使用します。これは前の例とほとんど同じです)。
WG21ペーパー N4382 には次の提案があります:
C.6 Range Facade and Adapter Utilities [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; }
};
プリミティブ範囲の基礎となる表現がわからない場合、それからイテレーターを抽出する方法は? range -for
で使用できる範囲にこれを適応させる方法は? 1つの方法(@ -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;
}
Live Example g ++ -std = c ++ 1zを使用( Assembly gcc.godbolt.orgを使用)
結論:センチネルはデリミタを型システムに押し込むだけのかわいいメカニズムではなく、 プリミティブ「Dスタイル」範囲 (それ自体はイテレータの概念を持たない場合があります)を新しいC++ 1z範囲のゼロオーバーヘッド抽象化としてサポートします。
新しい仕様では、__begin
を__end
と比較して不等式である限り、__end
と__begin
を異なるタイプにすることができます。 __end
はイテレータである必要はなく、述語にすることもできます。 begin
とend
のメンバーを定義する構造体を持つ愚かな例です。後者は反復子ではなく述語です:
#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"
}