Невинний діапазон на основі циклу не працює


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

Спробуйте це на Godbolt

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

Тепер у моєму фактичному випадку список складається із змінних членів класу.

Тепер це не працює, оскільки вираз стає тим, initializer_list<int>що насправді копіює a, b, c і d - отже, також не дозволяє модифікувати.

Моє запитання двояке:

Чи є якась мотивація, що не дозволяє таким чином написати діапазон на основі циклу? напр. можливо, може бути особливий випадок для голих дужних виразів.

Який синтаксичний акуратний спосіб фіксації цього типу петлі?

Щось у цьому напрямку було б кращим:

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 Я прямо заявив, що я не зацікавлений у вирішенні непрямості вказівника.
darune

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 і не використовую варіативний шаблон)
darune

4

Відповідно до стандарту § 11.6.4 Ініціалізація списку / p5 [dcl.init.list] [ Підкреслення шахти ]:

Об'єкт типу 'std :: inicijalizer_list' побудований зі списку ініціалізатора так, ніби реалізація створила та матеріалізувала (7.4) перше значення типу «масив N const E» , де N - кількість елементів у списку ініціалізатора. Кожен елемент цього масиву ініціалізується з копією відповідним елементом списку ініціалізатора, а об’єкт std :: inicijalizer_list побудований для посилання на цей масив. [Примітка: Конструктор або функція перетворення, вибрані для копії, повинні бути доступними (п. 14) у контексті списку ініціалізатора. - кінцева примітка] Якщо для ініціалізації будь-якого з елементів потрібна конверсія звуження, програма неправильно формується.

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

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

Демонстраційна демонстрація


@ Jarod42 Ouups вибачте, змінив це.
101010

Ви рішення не відповідає моїм критеріям приємного рішення - якби я був задоволений цим, я б у першу чергу не питав :)
darune

також ваша цитата не намагається відповісти на моє запитання
darune

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

Ви не можете просто використовувати std::array<std::reference_wrapper>>?
Toby Speight

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?
Toby Speight

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

Демонстраційна демонстрація

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.