[解決済み】カスタムタイプを「範囲ベースのforループ」で動作させる方法とは?
質問
最近、多くの人と同じように、私もC++11がもたらす様々な機能を試しています。私のお気に入りの1つは、「範囲ベースのforループ」です。
それはわかります。
for(Type& v : a) { ... }
と同等である。
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
そして、その
begin()
を返すだけです。
a.begin()
は標準的なコンテナの場合です。
しかし、もし私が カスタムタイプのquot;範囲ベースのfor loop(quot;ループ)を意識させる。 ?
を特殊化すればよいのでしょうか?
begin()
と
end()
?
カスタムタイプが名前空間に属している場合
xml
を定義する必要があります。
xml::begin()
または
std::begin()
?
要するに、そのための指針は何なのか。
どのように解決するのですか?
質問(とほとんどの回答)が投稿された後に、規格が変更されました この不具合報告の解決に .
を作る方法です。
for(:)
ループは、あなたのタイプで動作します。
X
は、現在2つの方法のどちらかです。
-
メンバー作成
X::begin()
とX::end()
は、イテレータのように動作するものを返します。 -
自由な関数を作成する
begin(X&)
とend(X&)
と同じ名前空間で、イテレータのように動作するものを返すものです。X
.¹
また、同様のことを
const
のバリエーションがあります。 これは、不具合報告の変更を実装しているコンパイラでも、そうでないコンパイラでも有効です。
返されるオブジェクトは、実際にイテレータである必要はありません。 そのため
for(:)
ループは、C++標準のほとんどの部分とは異なり
と同等のものに展開するよう指定されています。
:
for( range_declaration : range_expression )
になります。
{
auto && __range = range_expression ;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
で始まる変数が
__
は説明のためだけのものであり
begin_expr
と
end_expr
を呼び出すマジックです。
begin
/
end
.²
begin/end の戻り値に関する要件は単純である。をオーバーロードする必要があります。
++
が有効であること、初期化式が有効であること、バイナリ
!=
はブーリアンコンテキストで使用できるもの、単項は
*
初期化できるものを返すもの
range_declaration
で、パブリックなデストラクタを公開します。
イテレータと互換性のない方法でこれを行うのは、おそらく悪い考えでしょう。将来のC++の反復は、そうすればあなたのコードを壊すことに比較的寛大になるかもしれません。
余談ですが、将来の標準規格の改定で
end_expr
とは異なる型を返すように
begin_expr
. これは,手書きのC言語ループと同程度の効率に最適化しやすい遅延終了評価(NULL終端検出など)を可能にするなどの利点があります.
¹ 注意事項
for(:)
ループは、任意のテンポラリを
auto&&
変数に格納し、それを lvalue として渡します。 一時的な値(あるいは他のr値)に対して反復処理をしているかどうかを検出することはできません。
for(:)
ループを使用します。 n4527 の [stmt.ranged] 1.2-1.3 を参照してください。
² どちらかを呼び出します。
begin
/
end
メソッド、または自由関数の ADL ルックアップのみ
begin
/
end
,
または
マジックで C スタイルの配列をサポートします。 なお
std::begin
が呼び出されない限り
range_expression
の型のオブジェクトを返します。
namespace std
またはそれに依存している。
で c++17 range-for 式が更新されました。
{
auto && __range = range_expression ;
auto __begin = begin_expr;
auto __end = end_expr;
for (;__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
の種類で
__begin
と
__end
は切り離されました。
これにより、end iteratorがbeginと同じ型でないことが許容されます。 終了イテレータの型は、quot;sentinel" のみサポートすることができます。
!=
をbeginイテレータタイプとする。
なぜこれが便利かというと、エンドイテレータが、"check your
char*
を指しているかどうかを確認するために
'0'
の場合
==
を持つ
char*
. これにより、C++ の range-for 式は、ヌル文字で終端する
char*
バッファを使用します。
struct null_sentinal_t {
template<class Rhs,
std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
>
friend bool operator==(Rhs const& ptr, null_sentinal_t) {
return !*ptr;
}
template<class Rhs,
std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
>
friend bool operator!=(Rhs const& ptr, null_sentinal_t) {
return !(ptr==null_sentinal_t{});
}
template<class Lhs,
std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
>
friend bool operator==(null_sentinal_t, Lhs const& ptr) {
return !*ptr;
}
template<class Lhs,
std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
>
friend bool operator!=(null_sentinal_t, Lhs const& ptr) {
return !(null_sentinal_t{}==ptr);
}
friend bool operator==(null_sentinal_t, null_sentinal_t) {
return true;
}
friend bool operator!=(null_sentinal_t, null_sentinal_t) {
return false;
}
};
実例 のことです。
最小限のテストコードは
struct cstring {
const char* ptr = 0;
const char* begin() const { return ptr?ptr:""; }// return empty string if we are null
null_sentinal_t end() const { return {}; }
};
cstring str{"abc"};
for (char c : str) {
std::cout << c;
}
std::cout << "\n";
以下は簡単な例です。
namespace library_ns {
struct some_struct_you_do_not_control {
std::vector<int> data;
};
}
あなたのコードです。
namespace library_ns {
int* begin(some_struct_you_do_not_control& x){ return x.data.data(); }
int* end(some_struct_you_do_not_control& x){ return x.data.data()+x.data.size(); }
int const* cbegin(some_struct_you_do_not_control const& x){ return x.data.data(); }
int* cend(some_struct_you_do_not_control const& x){ return x.data.data()+x.data.size(); }
int const* begin(some_struct_you_do_not_control const& x){ return cbegin(x); }
int const* end(some_struct_you_do_not_control const& x){ return cend(x); }
}
これは、制御していない型を拡張して反復可能にする方法の例です。
ここでは、ポインターをイテレータとして返すことで、フードの下にベクトルがあることを隠しています。
自分が持っている型には、メソッドを追加することができます。
struct egg {};
struct egg_carton {
auto begin() { return eggs.begin(); }
auto end() { return eggs.end(); }
auto cbegin() const { return eggs.begin(); }
auto cend() const { return eggs.end(); }
auto begin() const { return eggs.begin(); }
auto end() const { return eggs.end(); }
private:
std::vector<egg> eggs;
};
を再利用しています。
vector
のイテレータです。 私は
auto
は簡潔である。
c++11
もっと冗長にしないと。
以下は、簡単で汚い反復可能なレンジビューです。
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
std::size_t size() const { return end()-begin(); }
bool empty() const { return begin()==end(); }
range_t without_back( std::size_t n = 1 ) const {
n = (std::min)(n, size());
return {begin(), end()-n};
}
range_t without_front( std::size_t n = 1 ) const {
n = (std::min)(n, size());
return {begin()+n, end()};
}
decltype(auto) front() const { return *begin(); }
decltype(auto) back() const { return *(std::prev(end())); }
};
template<class C>
auto make_range( C&& c ) {
using std::begin; using std::end;
return range_t{ begin(c), end(c) };
}
を使って c++17 テンプレートクラスの控除です。
std::vector<int> v{1,2,3,4,5};
for (auto x : make_range(v).without_front(2) ) {
std::cout << x << "\n";
}
は 3 4 5 を表示し、最初の 2 はスキップします。
関連
-
[解決済み】コンストラクターでのエラー:識別子を期待されますか?
-
[解決済み】識別子 "string "は未定義?
-
[解決済み] 式はクラス型を持つ必要があります。
-
[解決済み】ファイルから整数を読み込んで配列に格納する C++ 【クローズド
-
[解決済み】エラー:不完全な型へのメンバーアクセス:前方宣言の
-
[解決済み】CMakeエラー at CMakeLists.txt:30 (project)。CMAKE_C_COMPILER が見つかりませんでした。
-
[解決済み] 型名の後の括弧は、newで違いがあるのでしょうか?
-
[解決済み] std::mapで範囲指定for()ループを使用するには?
-
[解決済み] C++11逆レンジベースforループ
-
[解決済み] 範囲ベースのforループで転送参照を使用する利点は何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】coutはstdのメンバではない
-
[解決済み] string does not name a type Errorが発生するのはなぜですか?
-
[解決済み】Cygwin Make bash コマンドが見つかりません。
-
[解決済み】関数名の前に期待されるイニシャライザー
-
[解決済み】'cout'は型名ではない
-
[解決済み】1つ以上の多重定義されたシンボルが見つかる
-
[解決済み] 数値定数の前にunqualified-idを付けて、数値を定義することを期待する。
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件
-
[解決済み] 変数サイズのオブジェクトが初期化されないことがある c++
-
[解決済み】エラー。引数リストに一致するコンストラクタのインスタンスがない