표준 for 루프를 고려하십시오.
for (int i = 0; i < 10; ++i)
{
// do something with i
}
변수 i가 본문에서 수정되는 것을 방지하고 싶습니다 .for루프 .
그러나, 나는 선언 할 수 없습니다 i로 const이 증가 문을 유효하게한다. 증분 문 외부 i에서 const변수 를 만드는 방법이 있습니까?
표준 for 루프를 고려하십시오.
for (int i = 0; i < 10; ++i)
{
// do something with i
}
변수 i가 본문에서 수정되는 것을 방지하고 싶습니다 .for루프 .
그러나, 나는 선언 할 수 없습니다 i로 const이 증가 문을 유효하게한다. 증분 문 외부 i에서 const변수 를 만드는 방법이 있습니까?
const int i인수가 있는 함수로 바꿉니다. 인덱스의 가변성은 필요한 경우에만 노출되며 inline키워드를 사용 하여 컴파일 된 출력에 영향을주지 않도록 할 수 있습니다 .
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. ();루프의 끝에서 단순히 람다 즉시 호출된다는 것을 의미한다.
&캡처 에 대해 불평 할 것입니다. 이는 각 참조를 명시 적으로 캡처하도록 강제합니다. 성가신. 또한 이것이 작성자가를 잊어 버리고 ()코드가 호출되지 않도록 하는 쉬운 버그로 이어질 수 있다고 생각합니다 . 이것은 코드 검토에서도 놓칠 수있을만큼 충분히 작습니다.
[&]AUTOSAR (규칙 A5-1-2), HIC ++ 및 MISRA (확실하지 않음)와 같은 코딩 표준과 충돌하기 때문에 일반 캡처에 플래그를 지정합니다 . 그것은 정확하지 않다는 것이 아닙니다. 조직이 표준을 준수하기 위해 이러한 유형의 코드를 금지하는 것입니다. 에 관해서는 (), 최신 GCC 버전이 플래그를하지 않습니다 도 함께 -Wextra. 나는 여전히 접근 방식이 깔끔하다고 생각합니다. 많은 조직에서 작동하지 않습니다.
Cigien의 std::views::iota답변 을 좋아 하지만 C ++ 20 이상에서 작동하지 않는 사람에게는 간단하고 가벼운 버전을 구현하는 것이 다소 간단합니다.std::views::iota 호환 가능C ++ 11 이상.
필요한 것은 다음과 같습니다.
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으로 전환하려는 경우 쉬운 업그레이드 경로를 제공합니다.
int다음 시작 / 종료를 반환하는 "범위"클래스를 생성합니다.
KISS 버전 ...
for (int _i = 0; _i < 10; ++_i) {
const int i = _i;
// use i here
}
사용 사례가 루프 인덱스의 우발적 수정을 막기위한 것이라면 이러한 버그가 명백해집니다. ( 의도적 인 수정 을 막고 싶다면 행운을 빕니다 ...)
_. 그리고 약간의 설명 (예 : 범위)이 도움이 될 것입니다. 그렇지 않으면 예, 멋지게 KISSy.
i_것이 더 호환됩니다.
_i에서 여전히 수정할 수 있습니다.
std::views::iota완전한 방탄 방법을 위해 C ++ 20없이 갈 가치가 있습니다. 답변의 텍스트는 제한 사항과 질문에 대한 답변 방법을 설명합니다. 지나치게 복잡한 C ++ 11은 읽기 쉽고 유지 관리가 쉬운 IMO라는 측면에서 질병보다 치료법을 악화시킵니다. 이것은 C ++을 아는 모든 사람에게 여전히 매우 읽기 쉽고 관용구로서 합리적으로 보입니다. (하지만 선행 밑줄 이름은 피해야합니다.)
_Uppercase이며 double__underscore식별자는 예약되어 있습니다. _lowercase식별자는 전역 범위에서만 예약됩니다.
액세스 할 수없는 경우 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접근 방식은 최악이다.
vector. 범위가 매우 크면 이는 나쁠 수 있습니다.
std::vector는 범위가 작 으면 상대적인 규모에서 꽤 끔찍하며 여러 번 실행되는 작은 내부 루프라고 가정하면 매우 나쁠 수 있습니다. 일부 컴파일러 (libc ++가있는 clang, libstdc ++는 제외)는 함수를 이스케이프하지 않는 할당의 신규 / 삭제를 최적화 할 수 있지만, 그렇지 않으면 작은 완전 풀림 루프와 new+에 대한 호출 간의 차이가 될 수 있습니다. delete, 그리고 아마도 실제로 그 메모리에 저장합니다.
const i대부분의 경우 비용을 절감 할 수있는 C ++ 20 방법 없이는 오버 헤드의 가치가 없습니다. 특히 컴파일러가 모든 것을 최적화 할 가능성이 적은 런타임 변수 범위를 사용합니다.
다음은 C ++ 11 버전입니다.
for (int const i : {0,1,2,3,4,5,6,7,8,9,10})
{
std::cout << i << " ";
// i = 42; // error
}
{..}. 이 기능을 활성화하려면 무언가를 포함해야합니다. 예를 들어 적절한 헤더를 추가하지 않으면 코드가 손상됩니다 : godbolt.org/z/esbhra . <iostream>다른 헤더 에 대해 릴레이 하는 것은 나쁜 생각입니다!
#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
}
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 루프 가 아닐 수도 있지만 ,이 접근 방식은 다른 언어에서 매우 일반적입니다. 기능적 래퍼는 복잡한 문에서 구성 가능성이 매우 매끄럽고 사용하기에 매우 인체 공학적 일 수 있습니다.
이 코드는 작성, 이해 및 유지 관리도 간단합니다.
[&]또는 [=]) 에 대한 기본 캡처를 금지한다는 것입니다 . 이로 인해 각 구성원이 수동으로 캡처해야하는 경우 람다가 부 풀릴 수 있습니다. 모든 조직이이 작업을 수행하는 것은 아니므로 대답이 아닌 주석으로 만 언급합니다.
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