Невинный диапазон на основе петли не работает


11

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

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

Попробуй это на Годболте

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

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

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

У меня вопрос двоякий:

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

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

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

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

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


1
Простое решение (что я бы не использовать сам), чтобы создать список указателей вместо: { &a, &b, &c, &d }.
Какой-то программист чувак

2
initializer_listв основном вид на constмассив.
Jarod42

То, что я, вероятно, сделал бы, чтобы явно инициализировать переменные, одну за другой. Это не будет намного больше, чтобы написать, это ясно и ясно, и это делает то, что предназначено. :)
Какой-то программист чувак

3
если вы не хотите { &a, &b, &c, &d }, вы не захотите ни:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42

Вопросы «почему это не работает?» - это вопрос, который сильно отличается от «что я могу сделать, чтобы сделать что-то подобное этой работе?»
Николь Болас

Ответы:


4

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

Ближайший, вероятно, вы сможете прийти это:

#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
Вы можете бросить std::vector<int*>.
Jarod42

@mhhollomon Я прямо заявил, что не заинтересован в решении проблемы косвенного обращения указателей.
Даруна

1
Так должно быть auto sили auto* sнет auto& s.
LF

@darune - я буду рад, если кто-нибудь даст другой ответ. Не ясно, что такой ответ существует с действующим стандартом.
mhhollomon

@LF - согласился.
mhhollomon

4

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

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

2
Это достойное и хорошее решение / обходной путь. Идея похожа на мой собственный ответ (я использовал std :: reference_wrapper и не использовал шаблон variadic)
darune

4

Согласно стандарту §11.6.4 List-initialization / p5 [dcl.init.list] [ Выделение шахты ]:

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

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

Вы можете решить эту проблему, введя контейнер вместо списка инициализатора (например, `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;
}

Live Demo


@ Jarod42 Ой, извини, поправил это.
101010

Ваше решение не соответствует моим критериям для хорошего решения - если бы я был доволен этим, я бы не спросил в первую очередь :)
darune

также ваша цитата не пытается ответить на мой вопрос
darune

1
@darune - на самом деле, цитата является причиной того, что вы for (auto& s : {a, b, c, d})не работаете. Относительно того, почему в стандарте есть такой пункт ... вам нужно спросить членов комитета по стандартизации. Подобно многим таким вещам, аргументация может быть чем-то между: «Мы не посчитали, что ваш конкретный случай был достаточно полезен, чтобы его беспокоить», - «слишком много других частей стандарта пришлось бы изменить, чтобы поддержать ваш случай, и мы отложили рассмотрение всего этого, пока мы не разработаем будущий стандарт ".
Питер

Вы не можете просто использовать std::array<std::reference_wrapper>>?
Тоби Спейт

1

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

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

демонстрация


1
Чем это отличается от std::reference_wrapper?
Тоби Спейт

1
@TobySpeight: std::reference_wrapperпотребуется s.get() = 1;.
Jarod42

0

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

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

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


-1

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

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

Live демо

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.