На основе диапазона для С инициализатором скобки над значениями non-const?

Я пытаюсь перебрать несколько std::lists, сортировка каждого из них. Это наивный подход:

#include<list>
using namespace std;
int main(void){
    list<int> a,b,c;
    for(auto& l:{a,b,c}) l.sort();
}

производства

aa.cpp:5:25: error: no matching member function for call to 'sort'
        for(auto& l:{a,b,c}) l.sort();
                             ~~^~~~
/usr/bin/../lib64/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/bits/stl_list.h:1586:7: note: 
      candidate function not viable: 'this' argument has type 'const
      std::list<int, std::allocator<int> >', but method is not marked const
      sort();
      ^
/usr/bin/../lib64/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/bits/stl_list.h:1596:9: note: 
      candidate function template not viable: requires 1 argument, but 0 were
      provided
        sort(_StrictWeakOrdering);
        ^
1 error generated.

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

4 ответов


вы правильно угадали. std::initializer_list элементы всегда const (что составляет sort() ing их невозможно, как sort() - это неconst функция-член) и его элементы всегда копируются (что сделало бы sort()-ing их бессмысленно, даже если они не были const). Из [dcl.в этом.list], акцент мой:

объект типа std::initializer_list<E> создается из списка инициализаторов, как если бы реализация выделен временный массив из n элементов типа const E, где n-количество элементов в список инициализаторов. Каждый элемент этого массива -копирования-инициализации С соответствующим элементом инициализатора список, и std::initializer_list<E> объект построен для ссылки на этот массив. [ Примечание: конструктор или функция преобразования, выбранная для копии, должна быть доступна (пункт 11) в контексте инициализатора список. -конец Примечание ] если сужающее преобразование требуется инициализировать любой из элементов, программа плохо сформирована. [ пример:

struct X {
    X(std::initializer_list<double> v);
};
X x{ 1,2,3 };

инициализация будет реализована способом, примерно эквивалентным этому:

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

предполагая, что реализация может построить initializer_list объект с парой указателей. -конец пример ]

нет способа сделать их не-const или не скопированными. Решение указателя работает:

for (auto l : {&a, &b, &c}) l->sort();

потому что это указатель это const, а не элемент, на который он указывает. Другой альтернативой было бы написать шаблон вариационной функции:

template <typename... Lists>
void sortAll(Lists&&... lists) {
    using expander = int[];
    expander{0, (void(lists.sort()), 0)...};
}

sortAll(a, b, c);

вы также можете, я думаю, написать помощник, чтобы обернуть ваши списки в массив reference_wrapper to list<int> (поскольку вы не можете иметь массив ссылок), но это, наверное, более запутанной, чем полезно:

template <typename List, typename... Lists>
std::array<std::reference_wrapper<List>, sizeof...(Lists) + 1>
as_array(List& x, Lists&... xs) {
    return {x, xs...}; 
}

for (list<int>& l : as_array(a, b, c)) {  // can't use auto, that deduces
    l.sort();                             // reference_wrapper<list<int>>,
}                                         // so would need l.get().sort()

можно написать функцию ref_range что позволяет вам сделать это:

for(auto& l : ref_range(a,b,c)) {
    l.sort();
}

как говорили другие, как только вы пишете {a,b,c} вы застряли с initializer_list, и такой список всегда копирует своих доводов. Копии const (отсюда и ошибки), но даже если бы ты неconst ссылка вы будете изменять копии a, b и c вместо оригиналов.

во всяком случае, вот ref_range. Он строит vector of reference_wrapper.

// http://stackoverflow.com/questions/31724863/range-based-for-with-brace-initializer-over-non-const-values
#include<list>
#include<functional>
#include<array>

template<typename T, std:: size_t N>
struct hold_array_of_refs {
    using vec_type = std:: array< std:: reference_wrapper<T>, N >;
    vec_type m_v_of_refs;
    hold_array_of_refs(vec_type && v_of_refs) : m_v_of_refs(std::move(v_of_refs)) { }
    ~hold_array_of_refs() { }
    struct iterator {
        typename vec_type :: const_iterator m_it;
        iterator(typename vec_type :: const_iterator it) : m_it(it) {}
        bool operator != (const iterator &other) {
            return this->m_it != other.m_it;
        }
        iterator& operator++() { // prefix
            ++ this->m_it;
            return *this;
        }
        T& operator*() {
            return *m_it;
        }
    };

    iterator begin() const {
        return iterator(m_v_of_refs.begin());
    }
    iterator end() const {
        return iterator(m_v_of_refs.end());
    }
};

template<typename... Ts>
using getFirstTypeOfPack = typename std::tuple_element<0, std::tuple<Ts...>>::type;


template<typename ...T>
auto ref_range(T&... args) -> hold_array_of_refs< getFirstTypeOfPack<T...> , sizeof...(args)> {
    return {{{ std:: ref(args)... }}}; // Why does clang prefer three levels of {} ?
}

#include<iostream>
int main(void){
    std:: list<int> a,b,c;
    // print the addresses, so we can verify we're dealing
    // with the same objects
    std:: cout << &a << std:: endl;
    std:: cout << &b << std:: endl;
    std:: cout << &c << std:: endl;
    for(auto& l : ref_range(a,b,c)) {
        std:: cout << &l << std:: endl;
        l.sort();
    }
}

на {...} синтаксис фактически создает std::initializer_list. Как говорится на связанной странице:

A std::initializer_list объект автоматически создается, когда:

  • [...]
  • a braced-init-list обязан auto, в том числе в диапазоне для цикла

и :

объект типа std::initializer_list<T> - это легкий прокси-объект, который обеспечивает доступ к массив объектов типа const T.

таким образом, вы не можете изменить объекты, доступные через это initialize_list. Ваши решения с указателями кажутся мне самыми простыми.


прямой ответ на ваш вопрос:

правильно ли я предполагаю, что инициализатор скобки создает копию эти списки?

Да, это первая проблема. Ваш код создаст копии ваших списков, отсортирует эти копии и, наконец, забудет отсортированные копии.

, это само по себе приведет к нерабочему коду. Ошибка компилятора указывает на вторую проблему: неявный тип l is list<int> const&, а не list<int>&. Поэтому компилятор жалуется, что sort() пытается изменить списки констант.

вы можете обойти эту вторую проблему с неприятным const_cast:

#include <list>
#include <iostream>
using namespace std;
int main(void){
    list<int> a,b,c;
    a.push_back(2);
    a.push_back(0);
    a.push_back(1);
    for(auto& l:{a,b,c}) const_cast<list<int>&>(l).sort();
    for(auto i:a) cout << i << endl;
}

однако это вызовет первую проблему: ваш список списков содержит копии, и сортируются только эти копии. Таким образом, конечный результат не то, что вы хотите:

2
0
1

самый простой обходной путь-создать список указателей на ваш списки:

#include <list>
#include <iostream>
using namespace std;
int main(void){
    list<int> a,b,c;
    a.push_back(2);
    a.push_back(0);
    a.push_back(1);
    for(auto l:{&a,&b,&c}) l->sort();
    for(auto i:a) cout << i << endl;
}

это даст желаемый результат:

0
1
2