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


82

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

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

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

그러나, 나는 선언 할 수 없습니다 i로 const이 증가 문을 유효하게한다. 증분 문 외부 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 SonarQube 및 cppcheck 와 같은 정적 분석 도구 는 [&]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.