How to make a for loop variable const with the exception of the increment statement?

后端 未结 9 1158
天涯浪人
天涯浪人 2020-12-24 04:26

Consider a standard for loop:

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

I want to prevent the variable i fr

相关标签:
9条回答
  • 2020-12-24 04:46

    One simple approach not yet mentioned here that works in any version of C++ is to create a functional wrapper around a range, similar to what std::for_each does to iterators. The user is then responsible for passing in a functional argument as a callback which will be invoked on each iteration.

    For example:

    // 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);
            }
        }
    };
    

    Where the use would be:

    numeric_range{0, 10}.for_each([](const auto& i){
       std::cout << i << " ";  // ok
       //i = 100;              // error
    });
    

    Anything older than C++11 would be stuck passing a strongly-named function pointer into for_each (similar to std::for_each), but it still works.

    Here's a demo


    Although this may not be idiomatic for for loops in C++, this approach is quite common in other languages. Functional wrappers are really sleek for their composability in complex statements and can be very ergonomic for use.

    This code is also simple to write, understand, and maintain.

    0 讨论(0)
  • 2020-12-24 04:51

    And here is a C++11 version:

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

    Here is live demo

    0 讨论(0)
  • 2020-12-24 04:55

    For anyone that likes Cigien's std::views::iota answer but isn't working in C++20 or above, it's rather straightforward to implement a simplified and lightweight version of std::views::iota compatible c++11 or above.

    All it requires is:

    • A basic "LegacyInputIterator" type (something that defines operator++ and operator*) that wraps an integral value (e.g. an int)
    • Some "range"-like class that has begin() and end() that returns the above iterators. This will allow it to work in range-based for loops

    A simplified version of this could be:

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

    I've defined the above with constexpr where it's supported, but for earlier versions of C++ like C++11/14, you may need to remove constexpr where it is not legal in those versions to do so.

    The above boilerplate enables the following code to work in pre-C++20:

    for (int const i : iota(0, 10))
    {
       std::cout << i << " ";  // ok
       i = 42;                 // error
    }
    

    Which will generate the same assembly as the C++20 std::views::iota solution and the classic for-loop solution when optimized.

    This works with any C++11-compliant compilers (e.g. compilers like gcc-4.9.4) and still produces nearly identical assembly to a basic for-loop counterpart.

    Note: The iota helper function is just for feature-parity with the C++20 std::views::iota solution; but realistically, you could also directly construct an iota_range{...} instead of calling iota(...). The former just presents an easy upgrade path if a user wishes to switch to C++20 in the future.

    0 讨论(0)
  • 2020-12-24 04:56
    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);
    }
    

    maybe call it for_i

    No overhead https://godbolt.org/z/e7asGj

    0 讨论(0)
  • 2020-12-24 05:04

    From c++20, you can use ranges::views::iota like this:

    for (int const i : std::views::iota(0, 10))
    {
       std::cout << i << " ";  // ok
       i = 42;                 // error
    }
    

    Here's a demo.


    From c++11, you can also use the following technique, which uses an IIILE (immediately invoked inline lambda expression):

    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
    

    Here's a demo.

    Note that [&,i] means that i is captured by non-mutable copy, and everything else is captured by mutable reference. The (); at the end of the loop simply means that the lambda is invoked immediately.

    0 讨论(0)
  • 2020-12-24 05:06

    If you do not have access to c++20, typical makeover using a function

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

    now you could

    for (const int i : makeRange(0, 10))
    {
       std::cout << i << " ";  // ok
       //i = 100;              // error
    }
    

    (See a Demo)


    Update: Inspired from the @Human-Compiler's comment, I was wondering weather the given answers have any difference in the case of performance. It turn out that, except for this approach, for all other approaches surprisingly have same performance (for the range [0, 10)). The std::vector approach is the worst.

    (See Online Quick-Bench)

    0 讨论(0)
提交回复
热议问题