インクリメントステートメントを除いて、forループ変数をconstにする方法は?


84

標準のforループについて考えてみましょう。

for (int i = 0; i < 10; ++i) 
{
   // do something with i
}

ループのi本体で変数が変更されないようにしたいfor

しかし、私は宣言できないiとしてconst、これは、インクリメントステートメント無効になりますよう。ようにする方法がありインクリメント文の変数の外には?iconst


4
これを行う方法はないと思います
Itay 2010

27
これは、問題を探すための解決策のように聞こえます。
ピートベッカー

14
forループの本体をconst int i引数付きの関数に変換します。インデックスの可変性は、必要な場合にのみ公開され、inlineキーワードを使用して、コンパイルされた出力に影響を与えないようにすることができます。
MontyThibault20年

4
何が(というより、誰が)インデックスの値を変更する可能性がありますか?あなたは自分自身を信用しませんか?多分同僚?@PeteBeckerに同意します。
Z4層

5
@ Z4-tierはい、もちろん私は自分自身を信用していません。私は間違いを犯していることを知っています。すべての優れたプログラマーは知っています。だからこそconst、そもそも好きなものがあります。
KonradRudolph20年

回答:


120

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が変更不可能なコピーによってキャプチャされ、それ以外はすべて変更可能な参照によってキャプチャされることを意味することに注意してください。();ループの終わりには、単にラムダがすぐに呼び出されることを意味しています。


これが提供するものは非常に非常に一般的な構造のより安全な代替手段であるため、ほとんどの場合、特別なforループ構造が必要です。
MichaelDorgan20年

2
@MichaelDorganさて、この機能のライブラリサポートがあるので、コア言語機能として追加する価値はありません。
cigien

1
公平ですが、私の実際の仕事のほとんどは、せいぜいCまたはC ++ 11のままです。それは私のために、将来的に重要な場合には、私は...ただ勉強
マイケル・ドーガン

9
ラムダで追加したC ++ 11のトリックは巧妙ですが、私が行ったほとんどの職場では実用的ではありません。静的分析では、一般化された&キャプチャに不満があり、各参照を明示的にキャプチャする必要があります。面倒。また、これにより()、作成者がを忘れてコードが呼び出されないという簡単なバグが発生する可能性があると思います。これは簡単に小さいので、コードレビューでも見逃してしまいます。
ヒトコンパイラ

1
@cigien SonarQubeやcppcheckなどの静的分析ツールは、[&]AUTOSAR(ルールA5-1-2)、HIC ++などのコーディング標準と競合するため、一般的なキャプチャにフラグを立てます。MISRAもそうだと思います(わからない)。それが正しくないということではありません。組織は、このタイプのコードを標準に準拠することを禁止しています。については()、最新のgccバージョンは-Wextra。を使用してもこれにフラグを立てません。私はまだアプローチがきちんとしていると思います。多くの組織では機能しません。
ヒトコンパイラ

44

Cigienのstd::views::iota答えが好きで、C ++ 20以上で動作していない人にとっては、std::views::iota互換性のある単純化された軽量バージョンを実装するのはかなり簡単です。 以上。

必要なのは次のとおりです。

  • 基本的な「LegacyInputIterator」タイプ(を定義し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::iotafor最適化ループソリューションれます。

これは、C ++ 11準拠のコンパイラ(たとえば、のようなコンパイラgcc-4.9.4)で機能し、基本的なループの対応物とほぼ同じアセンブリを生成しforます。

注:iotaヘルパー関数は、単にC ++ 20との機能パリティ用でstd::views::iota解決。しかし現実的には、をiota_range{...}呼び出す代わりに直接構築することもできますiota(...)。前者は、ユーザーが将来C ++ 20に切り替えたい場合に、簡単なアップグレードパスを提供するだけです。


3
少し定型的なものが必要ですが、実際には、何をしているのかという点ではそれほど複雑ではありません。これは実際には単なる基本的なイテレーターパターンですが、をラップしてintから「範囲」クラスを作成して開始/終了を返します
Human-Compiler

1
スーパー重要な、しかし、あなたは:)少しあなたの答えの最初の行を言い替えるする場合がありますので、私はまた、誰も投稿しないことをC ++ 11溶液を加えていない
cigien

誰が反対票を投じたかはわかりませんが、私の回答が不十分であると感じた場合は、フィードバックをいただければ幸いです。反対票は、回答が質問に適切に対処していないと感じていることを示すための優れた方法ですが、この場合、私が改善できる回答に既存の批判や明らかな誤りはありません。
ヒトコンパイラ

人間コンパイラ@私も同時にDVを持って、彼らはどちらかの理由についてはコメントしなかった:(推測では、誰かが範囲抽象好きではない、私はそれについて心配しないだろう。。
cigien

1
「アセンブリ」は「荷物」や「水」のような大衆名詞です。通常の言い回しは、「C ++ 20と同じアセンブリにコンパイルされます...」です。単一の関数に対するコンパイラのasm出力は、単一のアセンブリではなく「アセンブリ」(アセンブリ言語命令のシーケンス)です。
ピーター

29

KISSバージョン...

for (int _i = 0; _i < 10; ++_i) {
    const int i = _i;

    // use i here
}

ユースケースがループインデックスの偶発的な変更を防ぐことだけである場合、これはそのようなバグを明らかにするはずです。(意図的な変更を防ぎたい場合は、幸運を祈ります...)


11
で始まる魔法の識別子を使用するという間違ったレッスンを教えていると思います_。そして、少しの説明(例えばスコープ)が役立つでしょう。そうでなければ、はい、うまくKISSy。
Yunnosch

14
「隠れた」変数i_を呼び出すと、より準拠します。
Yirkha

9
これがどのように質問に答えるかはわかりません。ループ変数は_i、ループ内で変更可能です。
cigien

4
@cigien:IMO、この部分的な解決策はstd::views::iota、完全に防弾の方法としてC ++ 20なしで実行する価値がある限りです。回答のテキストは、その制限と、質問への回答の試みを説明しています。過度に複雑なC ++ 11の束は、読みやすく、保守しやすいIMOの点で、病気よりも治癒を悪化させます。これは、C ++を知っている人なら誰にとっても非常に読みやすく、イディオムとしては妥当なようです。(ただし、下線付きの名前は避けてください。)
PeterCordes20年

5
@Yunnoschのみ_Uppercasedouble__underscore識別子は予約されています。_lowercase識別子はグローバルスコープでのみ予約されています。
ローマンオダイスキー

13

iをconstとして受け入れる関数で、forループのコンテンツの一部またはすべてを移動することはできませんか?

提案されているいくつかのソリューションよりも最適ではありませんが、可能であれば、これは非常に簡単です。

編集:私は不明確になりがちなので、単なる例です。

for (int i = 0; i < 10; ++i) 
{
   looper( i );
}

void looper ( const int v )
{
    // do your thing here
}

12

にアクセスできない場合 、関数を使用した典型的な変身

#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アプローチは最悪です。

ここに画像の説明を入力してください

オンラインクイックベンチを参照


4
これはc ++ 20より前では機能しvectorますが、を使用する必要があるため、かなりのオーバーヘッドがあります。範囲が非常に大きい場合、これは悪い可能性があります。
ヒトコンパイラ

@ Human-Compiler:std::vector範囲が狭い場合、Aは相対的なスケールではかなりひどいものであり、これが何度も実行される小さな内部ループであると想定された場合、非常に悪い可能性があります。一部のコンパイラ(libc ++のclangのように、libstdc ++ではない)は、関数をエスケープしない割り当ての新規/削除を最適化できますが、そうでない場合、これは、小さな完全に展開されたループとnew+の呼び出しの違いになります。delete、そして多分実際にそのメモリに保存します。
PeterCordes20年

IMOのマイナーな利点はconst i、C ++ 20の方法がなければ、ほとんどの場合、オーバーヘッドの価値がありません。特に、コンパイラがすべてを最適化する可能性を低くするランタイム変数範囲の場合。
ピーターコーデス

10

そして、これがC ++ 11バージョンです。

for (int const i : {0,1,2,3,4,5,6,7,8,9,10})
{
    std::cout << i << " ";
    // i = 42; // error
}

これがライブデモです


6
最大数が実行時の値によって決定される場合、これはスケーリングされません。
ヒトコンパイラ

12
@ Human-Compilerリストを目的の値に拡張し、プログラム全体を動的に再コンパイルするだけです;)
MontyThibault20年

5
の場合については言及していません{..}。この機能をアクティブにするには、何かを含める必要があります。たとえば、適切なヘッダーを追加しないと、コードが壊れます:godbolt.org/z/esbhra<iostream>他のヘッダーを中継するのは悪い考えです!
JeJo

6
#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
}

1
これは簡単に最良の答えです。C ++の「変数シャドウイング」を利用して、識別子を元のステップ変数を参照するconst ref変数に解決することは、洗練されたソリューションです。または少なくとも、利用可能な最もエレガントなもの。
MaxBarraclough20年

4

ここでまだ言及されていない、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)に渡してスタックしますが、それでも機能します。

これがデモです


これforC ++のforループでは慣用的ではないかもしれませんが、このアプローチは他の言語では非常に一般的です。機能ラッパーは、複雑なステートメントでの構成可能性が非常に洗練されており、人間工学に基づいて使用できます。

このコードは、記述、理解、および保守も簡単です。


このアプローチで注意すべき1つの制限は、一部の組織では、ラムダ([&]または[=])でのデフォルトのキャプチャを禁止して、特定の安全基準に準拠することです。これにより、各メンバーを手動でキャプチャする必要があり、ラムダが肥大化する可能性があります。すべての組織がこれを行っているわけではないので、私はこれを回答ではなくコメントとしてのみ言及しています。
ヒューマンコンパイラ

0
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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.