표준 for 루프를 고려하십시오.
for (int i = 0; i < 10; ++i)
{
// do something with i
}
변수 i
가 본문에서 수정되는 것을 방지하고 싶습니다 .for
루프 .
그러나, 나는 선언 할 수 없습니다 i
로 const
이 증가 문을 유효하게한다. 증분 문 외부 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
호환 가능C ++ 11 이상.
필요한 것은 다음과 같습니다.
- 정수 값 (예 🙂 을 래핑하는 기본 ” 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::iota
for
최적화되면 솔루션 및 클래식 루프 솔루션 과 .
이것은 모든 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
}
사용 사례가 루프 인덱스의 우발적 수정을 막기위한 것이라면 이러한 버그가 명백해집니다. ( 의도적 인 수정 을 막고 싶다면 행운을 빕니다 …)
답변
액세스 할 수없는 경우 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
접근 방식은 최악이다.
답변
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
}