標準のforループについて考えてみましょう。
for (int i = 0; i < 10; ++i)
{
// do something with i
}
ループのi
本体で変数が変更されないようにしたいfor
。
しかし、私は宣言できないi
としてconst
、これは、インクリメントステートメント無効になりますよう。ようにする方法がありインクリメント文の変数の外には?i
const
標準のforループについて考えてみましょう。
for (int i = 0; i < 10; ++i)
{
// do something with i
}
ループのi
本体で変数が変更されないようにしたいfor
。
しかし、私は宣言できないi
としてconst
、これは、インクリメントステートメント無効になりますよう。ようにする方法がありインクリメント文の変数の外には?i
const
const int i
引数付きの関数に変換します。インデックスの可変性は、必要な場合にのみ公開され、inline
キーワードを使用して、コンパイルされた出力に影響を与えないようにすることができます。
const
、そもそも好きなものがあります。
回答:
c ++ 20から、ranges :: views :: iotaを次のように使用できます。
for (int const i : std::views::iota(0, 10))
{
std::cout << i << " "; // ok
i = 42; // error
}
これがデモです。
c ++ 11からは、IIILE(即時呼び出しインラインラムダ式)を使用する次の手法を使用することもできます。
int x = 0;
for (int i = 0; i < 10; ++i) [&,i] {
std::cout << i << " "; // ok, i is readable
i = 42; // error, i is captured by non-mutable copy
x++; // ok, x is captured by mutable reference
}(); // IIILE
これがデモです。
これ[&,i]
は、i
が変更不可能なコピーによってキャプチャされ、それ以外はすべて変更可能な参照によってキャプチャされることを意味することに注意してください。();
ループの終わりには、単にラムダがすぐに呼び出されることを意味しています。
&
キャプチャに不満があり、各参照を明示的にキャプチャする必要があります。面倒。また、これにより()
、作成者がを忘れてコードが呼び出されないという簡単なバグが発生する可能性があると思います。これは簡単に小さいので、コードレビューでも見逃してしまいます。
[&]
AUTOSAR(ルールA5-1-2)、HIC ++などのコーディング標準と競合するため、一般的なキャプチャにフラグを立てます。MISRAもそうだと思います(わからない)。それが正しくないということではありません。組織は、このタイプのコードを標準に準拠することを禁止しています。については()
、最新のgccバージョンは-Wextra
。を使用してもこれにフラグを立てません。私はまだアプローチがきちんとしていると思います。多くの組織では機能しません。
Cigienのstd::views::iota
答えが好きで、C ++ 20以上で動作していない人にとっては、std::views::iota
互換性のある単純化された軽量バージョンを実装するのはかなり簡単です。c ++ 11 以上。
必要なのは次のとおりです。
operator++
、operator*
整数値(例えばラップ)int
)begin()
、end()
それを返す「範囲」のようなクラス。これにより、範囲ベースのfor
ループで動作できるようになりますこれの簡略化されたバージョンは次のようになります。
#include <iterator>
// This is just a class that wraps an 'int' in an iterator abstraction
// Comparisons compare the underlying value, and 'operator++' just
// increments the underlying int
class counting_iterator
{
public:
// basic iterator boilerplate
using iterator_category = std::input_iterator_tag;
using value_type = int;
using reference = int;
using pointer = int*;
using difference_type = std::ptrdiff_t;
// Constructor / assignment
constexpr explicit counting_iterator(int x) : m_value{x}{}
constexpr counting_iterator(const counting_iterator&) = default;
constexpr counting_iterator& operator=(const counting_iterator&) = default;
// "Dereference" (just returns the underlying value)
constexpr reference operator*() const { return m_value; }
constexpr pointer operator->() const { return &m_value; }
// Advancing iterator (just increments the value)
constexpr counting_iterator& operator++() {
m_value++;
return (*this);
}
constexpr counting_iterator operator++(int) {
const auto copy = (*this);
++(*this);
return copy;
}
// Comparison
constexpr bool operator==(const counting_iterator& other) const noexcept {
return m_value == other.m_value;
}
constexpr bool operator!=(const counting_iterator& other) const noexcept {
return m_value != other.m_value;
}
private:
int m_value;
};
// Just a holder type that defines 'begin' and 'end' for
// range-based iteration. This holds the first and last element
// (start and end of the range)
// The begin iterator is made from the first value, and the
// end iterator is made from the second value.
struct iota_range
{
int first;
int last;
constexpr counting_iterator begin() const { return counting_iterator{first}; }
constexpr counting_iterator end() const { return counting_iterator{last}; }
};
// A simple helper function to return the range
// This function isn't strictly necessary, you could just construct
// the 'iota_range' directly
constexpr iota_range iota(int first, int last)
{
return iota_range{first, last};
}
上記をconstexpr
サポートされている場所で定義しましたが、C ++ 11/14などの以前のバージョンのC ++ではconstexpr
、それらのバージョンで合法でない場所を削除する必要がある場合があります。
上記のボイラープレートにより、次のコードがC ++ 20より前で機能するようになります。
for (int const i : iota(0, 10))
{
std::cout << i << " "; // ok
i = 42; // error
}
これは、C ++ 20ソリューションおよびクラシックと同じアセンブリを生成しますstd::views::iota
for
最適化ループソリューションれます。
これは、C ++ 11準拠のコンパイラ(たとえば、のようなコンパイラgcc-4.9.4
)で機能し、基本的なループの対応物とほぼ同じアセンブリを生成しfor
ます。
注:iota
ヘルパー関数は、単にC ++ 20との機能パリティ用でstd::views::iota
解決。しかし現実的には、をiota_range{...}
呼び出す代わりに直接構築することもできますiota(...)
。前者は、ユーザーが将来C ++ 20に切り替えたい場合に、簡単なアップグレードパスを提供するだけです。
int
から「範囲」クラスを作成して開始/終了を返します
KISSバージョン...
for (int _i = 0; _i < 10; ++_i) {
const int i = _i;
// use i here
}
ユースケースがループインデックスの偶発的な変更を防ぐことだけである場合、これはそのようなバグを明らかにするはずです。(意図的な変更を防ぎたい場合は、幸運を祈ります...)
_
。そして、少しの説明(例えばスコープ)が役立つでしょう。そうでなければ、はい、うまくKISSy。
i_
を呼び出すと、より準拠します。
_i
、ループ内で変更可能です。
std::views::iota
、完全に防弾の方法としてC ++ 20なしで実行する価値がある限りです。回答のテキストは、その制限と、質問への回答の試みを説明しています。過度に複雑なC ++ 11の束は、読みやすく、保守しやすいIMOの点で、病気よりも治癒を悪化させます。これは、C ++を知っている人なら誰にとっても非常に読みやすく、イディオムとしては妥当なようです。(ただし、下線付きの名前は避けてください。)
_Uppercase
とdouble__underscore
識別子は予約されています。_lowercase
識別子はグローバルスコープでのみ予約されています。
にアクセスできない場合 c ++ 20、関数を使用した典型的な変身
#include <vector>
#include <numeric> // std::iota
std::vector<int> makeRange(const int start, const int end) noexcept
{
std::vector<int> vecRange(end - start);
std::iota(vecRange.begin(), vecRange.end(), start);
return vecRange;
}
今、あなたはできる
for (const int i : makeRange(0, 10))
{
std::cout << i << " "; // ok
//i = 100; // error
}
(デモを参照)
更新:@ Human-Compilerのコメントから着想を得て、与えられた回答がパフォーマンスの場合に違いがあるのではないかと思っていました。このアプローチを除いて、他のすべてのアプローチでは、驚くべきことに(範囲に対して[0, 10)
)同じパフォーマンスを持っていることがわかります。std::vector
アプローチは最悪です。
vector
ますが、を使用する必要があるため、かなりのオーバーヘッドがあります。範囲が非常に大きい場合、これは悪い可能性があります。
std::vector
範囲が狭い場合、Aは相対的なスケールではかなりひどいものであり、これが何度も実行される小さな内部ループであると想定された場合、非常に悪い可能性があります。一部のコンパイラ(libc ++のclangのように、libstdc ++ではない)は、関数をエスケープしない割り当ての新規/削除を最適化できますが、そうでない場合、これは、小さな完全に展開されたループとnew
+の呼び出しの違いになります。delete
、そして多分実際にそのメモリに保存します。
const i
、C ++ 20の方法がなければ、ほとんどの場合、オーバーヘッドの価値がありません。特に、コンパイラがすべてを最適化する可能性を低くするランタイム変数範囲の場合。
そして、これがC ++ 11バージョンです。
for (int const i : {0,1,2,3,4,5,6,7,8,9,10})
{
std::cout << i << " ";
// i = 42; // error
}
{..}
。この機能をアクティブにするには、何かを含める必要があります。たとえば、適切なヘッダーを追加しないと、コードが壊れます:godbolt.org/z/esbhra。<iostream>
他のヘッダーを中継するのは悪い考えです!
#include <cstdio>
#define protect(var) \
auto &var ## _ref = var; \
const auto &var = var ## _ref
int main()
{
for (int i = 0; i < 10; ++i)
{
{
protect(i);
// do something with i
//
printf("%d\n", i);
i = 42; // error!! remove this and it compiles.
}
}
}
注:言語の驚くべき愚かさのために、スコープをネストする必要があります。for(...)
ヘッダーで宣言された変数は、{...}
複合ステートメントで宣言された変数と同じネストレベルであると見なされます。これは、たとえば次のことを意味します。
for (int i = ...)
{
int i = 42; // error: i redeclared in same scope
}
何?カーリーブレースを開けただけではありませんか?さらに、それは一貫性がありません:
void fun(int i)
{
int i = 42; // OK
}
ここでまだ言及されていない、C ++のどのバージョンでも機能する簡単なアプローチの1つstd::for_each
は、イテレーターの場合と同様に、範囲の周りに機能ラッパーを作成することです。次に、ユーザーは、各反復で呼び出されるコールバックとして機能引数を渡す責任があります。
例えば:
// A struct that holds the start and end value of the range
struct numeric_range
{
int start;
int end;
// A simple function that wraps the 'for loop' and calls the function back
template <typename Fn>
void for_each(const Fn& fn) const {
for (auto i = start; i < end; ++i) {
const auto& const_i = i;
fn(const_i);
}
}
};
使用場所:
numeric_range{0, 10}.for_each([](const auto& i){
std::cout << i << " "; // ok
//i = 100; // error
});
C ++ 11より古いものは、厳密に名前が付けられた関数ポインターをfor_each
(と同様にstd::for_each
)に渡してスタックしますが、それでも機能します。
これがデモです
これfor
はC ++のforループでは慣用的ではないかもしれませんが、このアプローチは他の言語では非常に一般的です。機能ラッパーは、複雑なステートメントでの構成可能性が非常に洗練されており、人間工学に基づいて使用できます。
このコードは、記述、理解、および保守も簡単です。
[&]
または[=]
)でのデフォルトのキャプチャを禁止して、特定の安全基準に準拠することです。これにより、各メンバーを手動でキャプチャする必要があり、ラムダが肥大化する可能性があります。すべての組織がこれを行っているわけではないので、私はこれを回答ではなくコメントとしてのみ言及しています。
template<class T = int, class F>
void while_less(T n, F f, T start = 0){
for(; start < n; ++start)
f(start);
}
int main()
{
int s = 0;
while_less(10, [&](auto i){
s += i;
});
assert(s == 45);
}
多分それを呼ぶ for_i
オーバーヘッドなしhttps://godbolt.org/z/e7asGj