Как правильно передать const_iterator & в функцию? - PullRequest
1 голос
/ 23 июня 2019

Предположим, у меня есть вектор целых чисел, и я хочу обработать его странным рекурсивным способом (эта ситуация может показаться странной без контекста, но все же).

Я хочу использовать const_iterators для отслеживания текущей позиции. Вот odd_recursive_stuff() прототип:

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

Сначала я попытался назвать это так:

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

К счастью, он не компилируется (например, в clang 8.0.0):

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

Поскольку std::begin() возвращает r-значение, я должен назвать его другим способом, который работает:

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

Теперь мне интересно , можно ли вызвать базу recursive_odd_stuff() одной строкой без local_variable it_beg?

Кажется, что невозможно написать другую версию begin(), которая возвращает l-значение, потому что "возвращаемое значение функции является l-значением тогда и только тогда, когда оно является ссылкой (C ++ 03) . (5.2.2 [expr.call] / 10) ". Таким образом, единственный способ - назвать его двумя строками?

Ответы [ 2 ]

2 голосов
/ 23 июня 2019

Перегрузка!

Иметь версию, которая принимает только rvalues:

void odd_recursive_stuff (std::vector<int>::const_iterator&& it_cur, 
                          std::vector<int>::const_iterator   it_end);

… и версию, которая принимает lvalue-ссылки (и делает дополнительную строку для вас):

void odd_recursive_stuff (const std::vector<int>::const_iterator& it_cur, 
                                std::vector<int>::const_iterator  it_end)
{
    std::vector<int>::const_iterator it_copy(it_cur);
    odd_recursive_stuff(std::move(it_copy), it_end);
}

Это тот же принцип, на котором основана семантика перемещения, потому что конструкторы копирования и перемещения выбираются одинаково.

Но вы можете подумать об отбрасывании всего этого и просто возвратевместо этого новое значение it:

std::vector<int>::const_iterator
odd_recursive_stuff(std::vector<int>::const_iterator it_cur, 
                    std::vector<int>::const_iterator it_end);

Вы можете свободно от него отказаться, если захотите.

Никто на самом деле не ожидает, что итератор будет взят по ссылке.

2 голосов
/ 23 июня 2019

Итак, есть способ сделать его однострочным, но я не рекомендую его:

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

Я думаю, что ваш n-й рекурсивный вызов изменяет ссылку, которая затем используетсяn-1 звонящий что-то делает.В этом случае я бы порекомендовал разделить код на две функции:

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

Это предоставляет общедоступный интерфейс, который просто требует итераторов.Позже, когда вы измените алгоритм так, чтобы не требовалась ссылка, или для него также потребуется end, вам не нужно менять все вызовы.

Первое решение можетразвернем что-то похожее на это:

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...
}

Лямбда operator() возвращает ссылку на локальную переменную, но она локальна для объекта лямбда, а не для самого operator().Поскольку лямбда-объект доживает до конца выражения (;), вызов безопасен.Просто отметьте, что я использовал std::ref как быстрый способ вернуть ссылку без необходимости явно указывать тип возвращаемого значения.std::reference_wrapper<T> тогда неявно преобразуется в T&.

return it; вернется по значению, и [it=std::begin(vec)]()mutable ->decltype(it)&{...}; также невозможно.->decltype(std::begin(vec))&{ работает, но многословно.Другой альтернативой является явное написание типа итератора или использование using, но это еще хуже.

...