Невинный диапазон на основе петли не работает - PullRequest
10 голосов
/ 06 ноября 2019

Следующее не компилируется:

#include <iostream>

int main()
{
    int a{},b{},c{},d{};

    for (auto& s : {a, b, c, d}) {
        s = 1;
    }
    std::cout << a << std::endl;
    return 0;
}

Попробуйте на Godbolt

Ошибка компилятора: error: assignment of read-only reference 's'

Теперь в моем конкретном случае список состоит из переменных-членов класса.

Теперь это не работает, потому что выражение становится initializer_list<int>, которое фактически копирует, b, c и d - следовательно, также не разрешаем модификацию.

Мой вопрос состоит из двух частей:

Есть ли какая-либо мотивация, которая не позволяет писать основанные на диапазоне значения дляцикл таким образом? например. возможно, может быть особый случай для выражений «голые скобки».

Что такое синтаксический аккуратный способ исправления этого типа цикла?

Что-то в этом направлении было бы предпочтительным:

for (auto& s : something(a, b, c, d)) {
    s = 1;
}

Я не считаю перенаправление указателя хорошим решением (то есть {&a, &b, &c, &d}) - любое решение должно давать ссылку на элемент непосредственно, когда итераторразыменовывается .

Ответы [ 6 ]

4 голосов
/ 06 ноября 2019

Просто еще одно решение в рамках идеи обертки:

template<typename T, std::size_t size>
class Ref_array {
    using Array = std::array<T*, size>;

    class Iterator {
    public:
        explicit Iterator(typename Array::iterator it) : it_(it) {}

        void operator++() { ++it_; }
        bool operator!=(const Iterator& other) const { return it_ != other.it_; }
        decltype(auto) operator*() const { return **it_; }

    private:
        typename Array::iterator it_;
    };

public:
    explicit Ref_array(Array args) : args_(args) {}

    auto begin() { return Iterator(args_.begin()); }
    auto end() { return Iterator(args_.end()); }

private:
    Array args_;
};

template<typename T, typename... Ts>
auto something(T& first, Ts&... rest) {
    static_assert((std::is_same_v<T, Ts> && ...));
    return Ref_array<T, 1 + sizeof...(Ts)>({&first, &rest...});
}

Тогда:

int main() {
    int a{}, b{}, c{}, d{};

    for (auto& s : something(a, b, c, d)) {
        std::cout << s;
        s = 1;
    }

    std::cout  << std::endl;
    for (auto& s : something(a, b, c, d))
        std::cout << s;
}

выводит

0000
1111
4 голосов
/ 06 ноября 2019

Согласно стандарту §11.6.4 Список-инициализация / p5 [dcl.init.list] [ Акцент на шахте ]:

AnОбъект типа 'std :: initializer_list' создается из списка инициализаторов , как если бы реализация генерировала и материализовала (7.4) значение типа «массив из N const E» , где N - количество элементовв списке инициализатора. Каждый элемент этого массива инициализируется копией с соответствующим элементом списка инициализатора, и объект std :: initializer_list создается для ссылки на этот массив. [Примечание: конструктор или функция преобразования, выбранные для копии, должны быть доступны (раздел 14) в контексте списка инициализатора. - примечание конца] Если для инициализации какого-либо элемента требуется сужающее преобразование, программа имеет некорректную форму.

Таким образом, ваш компилятор жалуется законно (т. Е. auto &s вычитает в int const& s и вы не можете присвоить s в цикле for ranged).

Вы можете решить эту проблему, введя контейнер вместо списка инициализатора (например, `std :: vector ') с помощью' std:: reference_wrapper ':

#include <iostream>
#include <vector>
#include <functional>

int main()
{
    int a{},b{},c{},d{};

    for (auto& s : std::vector<std::reference_wrapper<int>>{a, b, c, d}) {
        s.get()= 1;
    }
    std::cout << a << std::endl;
    return 0;
}

Демонстрационная версия

4 голосов
/ 06 ноября 2019

Диапазоны не так волшебны, как хотелось бы людям. В конце концов, должен существовать объект, для которого компилятор может генерировать вызовы либо для функции-члена, либо для свободной функции begin() и end().

Ближайший, который вы, вероятно, сможете получить:1005 *

#include <iostream>

int main()
{
    int a{},b{},c{},d{};

    for (auto s : {&a, &b, &c, &d} ) {
        *s = 1;
    }
    std::cout << a << "\n";
    return 0;
}
1 голос
/ 06 ноября 2019

Чтобы удовлетворить этот синтаксис

for (auto& s : something{a, b, c, d}) {
    s = 1;
}

, вы можете создать оболочку:

template <typename T>
struct MyRefWrapper
{
public:
    MyRefWrapper(T& p)  : p(&p) {}

    T& operator =(const T& value) const { return *p = value; }

    operator T& () const { return *p; }
private:
    T* p;     
};

Demo

0 голосов
/ 06 ноября 2019

Решение: используйте справочную обертку

template <class It>
struct range_view_iterator : public It{//TODO: don't inherit It
    auto& operator*() {
        return (*this)->get();
    }
};

template<class It>
range_view_iterator(It) -> range_view_iterator<It>;


template<class T>
struct range_view {
    std::vector<std::reference_wrapper<T> > refs_;
    range_view(std::initializer_list<std::reference_wrapper<T> > refs) : refs_{refs} {
    }

    auto begin() {
        return range_view_iterator{ refs_.begin() };
    }

    auto end() {
        return range_view_iterator{ refs_.end() };
    }
};

Затем используется как:

for (auto& e : range_view<int>{a, b, c, d}) {
    e = 1;
}

Это не пытается ответить на первый вопрос.

0 голосов
/ 06 ноября 2019

Вы можете создать класс-обертку для хранения ссылки, который будет иметь оператор присваивания для обновления этого значения:

template<class T>
struct Wrapper {
    T& ref;

    Wrapper(T& ref)
    : ref(ref){}

    template<class U>
    void operator=(U u) {
        ref = u;
    }
};

template<class...T>
auto sth(T&...t) {
    return std::array< Wrapper<std::common_type_t<T...> > ,sizeof...(t) >{Wrapper(t)...};
};

int main(){
    int a{},b{},c{},d{};

    for (auto s : sth(a,b,c,d)) {
        s = 1;
    }
    std::cout << a << std::endl; // 1

Демонстрационная версия

...