증분 문을 제외하고 for 루프 변수 const를 만드는 방법은 무엇입니까?


82

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

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

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

그러나, 나는 선언 할 수 없습니다 iconst이 증가 문을 유효하게한다. 증분 문 외부 i에서 const변수 를 만드는 방법이 있습니까?


4
나는 이것을 할 방법이 없다고 믿는다
Itay

27
이것은 문제를 찾는 해결책처럼 들립니다.
Pete Becker

14
for 루프의 본문을 const int i인수가 있는 함수로 바꿉니다. 인덱스의 가변성은 필요한 경우에만 노출되며 inline키워드를 사용 하여 컴파일 된 출력에 영향을주지 않도록 할 수 있습니다 .
Monty Thibault

4
당신 외에 어떤 (또는 오히려 누가) 지수의 가치를 바꿀 수 있습니까? 당신은 자신을 불신합니까? 동료일까요? @PeteBecker에 동의합니다.
Z4 계층

4
@ Z4-tier 네, 물론 나 자신을 불신합니다. 나는 내가 실수를한다는 것을 압니다. 좋은 프로그래머라면 누구나 알고 있습니다. 그것이 우리가 const시작하는 것과 같은 것이있는 이유 입니다.
Konrad Rudolph

답변:


119

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 루프 구조가 거의 필요합니다.
Michael Dorgan

2
@MichaelDorgan 이제이 기능에 대한 라이브러리 지원이 있으므로 핵심 언어 기능으로 추가 할 가치가 없습니다.
cigien

1
공정하지만 거의 모든 실제 작업은 여전히 ​​C 또는 C ++ 11입니다. 미래에 중요한 경우를 대비하여 공부합니다 ...
Michael Dorgan

9
람다와 함께 추가 한 C ++ 11 트릭은 깔끔하지만 제가 있었던 대부분의 작업장에서는 실용적이지 않습니다. 정적 분석은 일반화 된 &캡처 에 대해 불평 할 것입니다. 이는 각 참조를 명시 적으로 캡처하도록 강제합니다. 성가신. 또한 이것이 작성자가를 잊어 버리고 ()코드가 호출되지 않도록 하는 쉬운 버그로 이어질 수 있다고 생각합니다 . 이것은 코드 검토에서도 놓칠 수있을만큼 충분히 작습니다.
Human-Compiler

1
@cigien SonarQubecppcheck 와 같은 정적 분석 도구 는 [&]AUTOSAR (규칙 A5-1-2), HIC ++ 및 MISRA (확실하지 않음)와 같은 코딩 표준과 충돌하기 때문에 일반 캡처에 플래그를 지정합니다 . 그것은 정확하지 않다는 것이 아닙니다. 조직이 표준을 준수하기 위해 이러한 유형의 코드를 금지하는 것입니다. 에 관해서는 (), 최신 GCC 버전이 플래그를하지 않습니다 도 함께 -Wextra. 나는 여전히 접근 방식이 깔끔하다고 생각합니다. 많은 조직에서 작동하지 않습니다.
인간 컴파일러

44

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으로 전환하려는 경우 쉬운 업그레이드 경로를 제공합니다.


3
약간의 상용구가 필요하지만 실제로 수행하는 작업이 그렇게 복잡하지는 않습니다. 실제로는 기본 반복기 패턴이지만을 래핑 한 int다음 시작 / 종료를 반환하는 "범위"클래스를 생성합니다.
Human-Compiler

1
그다지 중요하지는 않지만 아무도 게시하지 않은 C ++ 11 솔루션을 추가 했으므로 답변의 첫 번째 줄을 약간
바꾸고 싶을 수

누가 반대표를 던 졌는지 잘 모르겠지만 제 답변이 만족스럽지 않아 개선 할 수 있도록 의견을 보내 주시면 감사하겠습니다. 반대 투표는 답변이 질문을 적절하게 다루지 않는다고 느끼는 것을 보여주는 좋은 방법이지만,이 경우에는 제가 개선 할 수있는 답변에 기존 비판이나 명백한 결함이 없습니다.
Human-Compiler

@ Human-Compiler 나도 DV를 동시에 받았는데 왜 둘 중 하나에 대해 언급하지 않았습니다. :( 누군가가 범위 추상화를 좋아하지 않는다고 생각합니다. 나는 그것에 대해 걱정하지 않을 것입니다.
cigien

1
"assembly"는 "luggage"또는 "water"와 같은 질량 명사입니다. 일반적인 구문은 " C ++ 20 과 동일한 어셈블리 로 컴파일됩니다 ..."입니다. 단일 함수에 대한 컴파일러의 asm 출력 단일 어셈블리 가 아니라 "어셈블리"(어셈블리 언어 명령어 시퀀스)입니다.
Peter Cordes

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 ++을 아는 모든 사람에게 여전히 매우 읽기 쉽고 관용구로서 합리적으로 보입니다. (하지만 선행 밑줄 이름은 피해야합니다.)
Peter Cordes

5
@Yunnosch 전용 _Uppercase이며 double__underscore식별자는 예약되어 있습니다. _lowercase식별자는 전역 범위에서만 예약됩니다.
Roman Odaisky

13

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

#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 참조 )


4
이것은 pre-c ++ 20에서 작동하지만 vector. 범위가 매우 크면 이는 나쁠 수 있습니다.
Human-Compiler

@ Human-Compiler : A std::vector는 범위가 작 으면 상대적인 규모에서 꽤 끔찍하며 여러 번 실행되는 작은 내부 루프라고 가정하면 매우 나쁠 수 있습니다. 일부 컴파일러 (libc ++가있는 clang, libstdc ++는 제외)는 함수를 이스케이프하지 않는 할당의 신규 / 삭제를 최적화 할 수 있지만, 그렇지 않으면 작은 완전 풀림 루프와 new+에 대한 호출 간의 차이가 될 수 있습니다. delete, 그리고 아마도 실제로 그 메모리에 저장합니다.
Peter Cordes

IMO의 사소한 이점은 const i대부분의 경우 비용을 절감 할 수있는 C ++ 20 방법 없이는 오버 헤드의 가치가 없습니다. 특히 컴파일러가 모든 것을 최적화 할 가능성이 적은 런타임 변수 범위를 사용합니다.
Peter Cordes

13

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

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

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

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

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

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
런타임 값에 의해 최대 수가 결정되면 확장되지 않습니다.
Human-Compiler

12
@ Human-Compiler 목록을 원하는 값으로 확장하고 전체 프로그램을 동적으로 다시 컴파일하십시오.)
Monty Thibault

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 변수로 확인되도록하는 것은 우아한 솔루션입니다. 또는 최소한 가장 우아한 것이 있습니다.
Max Barraclough

4

C ++의 모든 버전에서 작동하는 여기에 아직 언급되지 않은 간단한 접근 방식 중 하나는 범위 주변에 기능적 래퍼를 만드는 것입니다. 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 ) 되지만 여전히 작동합니다.

다음은 데모입니다.


이것은 C ++for 에서 관용적 인 for 루프 가 아닐 수도 있지만 ,이 접근 방식은 다른 언어에서 매우 일반적입니다. 기능적 래퍼는 복잡한 문에서 구성 가능성이 매우 매끄럽고 사용하기에 매우 인체 공학적 일 수 있습니다.

이 코드는 작성, 이해 및 유지 관리도 간단합니다.


이 접근 방식에서 알아야 할 한 가지 제한 사항은 일부 조직 에서 특정 안전 표준을 준수하기 위해 람다 (예 : [&]또는 [=]) 에 대한 기본 캡처를 금지한다는 것입니다 . 이로 인해 각 구성원이 수동으로 캡처해야하는 경우 람다가 부 풀릴 수 있습니다. 모든 조직이이 작업을 수행하는 것은 아니므로 대답이 아닌 주석으로 만 언급합니다.
Human-Compiler

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.