Search code examples
c++const-iterator

How to properly pass const_iterator& into a function?


Let's suppose I have a vector of integers and want to process it in an odd recursive manner (this situation might sound strange without a context, but still).

I want to use const_iterators to track current position. Here is odd_recursive_stuff() prototype:

// Note: changing "std::vector<int>::const_iterator& it_cur"
// into "std::vector<int>::const_iterator it_cur" will change
// the side effects!
void odd_recursive_stuff (std::vector<int>::const_iterator&  it_cur, 
                          std::vector<int>::const_iterator   it_end);

Firstly I tried to call it this way:

void process_vec (const std::vector<int> &vec) {
  odd_recursive_stuff (std::begin(vec), std::end(vec));
}

Luckily, It doesn't compile (in clang 8.0.0 for example):

Error: no matching function for call to 'recursive_odd_stuff'
Candidate function not viable: expects an l-value for 1st argument!

Because std::begin() returns r-value, so I have to call it another way that works:

void process_vec (const std::vector<int> &vec) {
   std::vector<int>::const_iterator it_beg = std::begin (vec);
   recursive_odd_stuff (it_beg, std::end(vec));
}

Now I'm wondering if it is possible to call the base of recursive_odd_stuff() with a single line without local_variable it_beg?

It seems that It is impossible to write another version of begin() which returns l-value, because "the return value of a function is an l-value if and only if it is a reference (C++03). (5.2.2 [expr.call] / 10)". So the only way is to call it with two lines?


Solution

  • So, there's a way to make it a one-liner, but I don't recommend it:

    #include <vector>
    #include <functional>
    
    void odd_recursive_stuff (std::vector<int>::const_iterator&  it_cur, 
                              std::vector<int>::const_iterator   it_end){}
    
    void process_vec (const std::vector<int> &vec) {
      odd_recursive_stuff ([it=std::begin(vec)]()mutable{return std::ref(it);}(), std::end(vec));
    }
    

    I think that your n-th recursive call changes the reference which is then used by the n-1th caller to do something. In this case, I would recommend splitting the code into two functions:

    odd_recursive_stuff(IT begin, IT end){
        odd_recursive_stuff_impl(begin, end);
    }
    odd_recursive_stuff_impl(IT& begin, IT& end){
        ....
    }
    

    This exposes a public interface that just requires iterators. Later when you change the algorithm in a way that does not require the reference, or it will require end to be a reference too, then you don't have to change all calls.

    The first solution might expand into something akin to this:

    void process_vec (const std::vector<int> &vec) {
        using IT = std::vector<int>::const_iterator;
        struct _lambda_type{
                _lambda_type(const IT& it):_it(it){}
    
                //By default lambda's () is const method, hence the mutable qualifier.
                std::reference_wrapper<IT> operator()()/*Not const*/{
                    return std::ref(_it);
                }
            private:
                IT _it;
        };
        //Previous lines...
        {//The line with the call.
            //Lambda is created before the call and lives until the expression is fully evaluated.
            _lambda_type lambda{std::begin(vec)};
            odd_recursive_stuff (lambda(), std::end(vec));
        }//Here's the lambda destroyed. So the call is perfectly safe.
        //The rest...
    }
    

    The lambda's operator() returns a reference to a local variable, but it's local to the lambda object, not the operator() itself. Because the lambda object lives until the end of the expression(;) the call is safe. Just note that I used std::ref as a quick way to return a reference without the need to mention the return type explicitly. std::reference_wrapper<T> is then implicitly convertible to T&.

    return it; would return by value and [it=std::begin(vec)]()mutable ->decltype(it)&{...}; is not possible either. ->decltype(std::begin(vec))&{ works but it's wordy. Another alternatives are to write the iterator's type explicitly or use a using but that's even worse.