증분 문을 제외하고 for 루프 변수 const를 만드는 방법은 무엇입니까? 10; ++i) { // do

표준 for 루프를 고려하십시오.

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

변수 i가 본문에서 수정되는 것을 방지하고 싶습니다 .for루프 .

그러나, 나는 선언 할 수 없습니다 iconst이 증가 문을 유효하게한다. 증분 문 외부 i에서 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. ();루프의 끝에서 단순히 람다 즉시 호출된다는 것을 의미한다.


답변

Cigien의 std::views::iota답변 을 좋아 하지만 C ++ 20 이상에서 작동하지 않는 사람에게는 간단하고 가벼운 버전을 구현하는 것이 다소 간단합니다.std::views::iota 호환 가능 이상.

필요한 것은 다음과 같습니다.

  • 정수 값 (예 🙂 을 래핑하는 기본 ” LegacyInputIterator “유형 ( operator++및 을 정의 operator*하는 것 int)
  • 가 일부 “범위”-like 클래스 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으로 전환하려는 경우 쉬운 업그레이드 경로를 제공합니다.


답변

KISS 버전 …

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

    // use i here
}

사용 사례가 루프 인덱스의 우발적 수정을 막기위한 것이라면 이러한 버그가 명백해집니다. ( 의도적 인 수정 을 막고 싶다면 행운을 빕니다 …)


답변

액세스 할 수없는 경우 , 기능을 사용한 전형적인 화장

#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접근 방식은 최악이다.

( Online Quick-Bench 참조 )


답변

i를 const로 받아들이는 함수에서 for 루프의 일부 또는 전체 내용을 이동할 수 없습니까?

제안 된 일부 솔루션보다 덜 최적이지만 가능하면 매우 간단합니다.

편집 : 내가 불분명 한 경향이있는 단지 예.

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

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

답변

다음은 C ++ 11 버전입니다.

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

라이브 데모입니다


답변

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