c++c++11initializer-list

Range-based for with brace-initializer over non-const values?


I am trying to iterate over a number of std::lists, sorting each of them. This is the naive approach:

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

producing

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.

Am I correctly guessing that brace-initializer is creating copy of those lists? And is there a way to not copy them, and make them modifiable inside the loop? (other than making list of pointers to them, which is my current workaround).


Solution

  • You are guessing correctly. std::initializer_list elements are always const (which makes sort()ing them impossible, as sort() is a non-const member function) and its elements are always copied (which would make sort()-ing them meaningless even if they weren't const). From [dcl.init.list], emphasis mine:

    An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. [ Note: A constructor or conversion function selected for the copy shall be accessible (Clause 11) in the context of the initializer list. —end note ] If a narrowing conversion is required to initialize any of the elements, the program is ill-formed. [ Example:

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

    The initialization will be implemented in a way roughly equivalent to this:

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

    assuming that the implementation can construct an initializer_list object with a pair of pointers. —end example ]

    There is no way to make them non-const or non-copied. The pointer solution works:

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

    because it's the pointer that's const, not the element it's pointing to. The other alternative would be to write a variadic function template:

    template <typename... Lists>
    void sortAll(Lists&&... lists) {
        // before C++17
        using expander = int[];
        expander{0, (void(lists.sort()), 0)...};
    
        // C++17 or later
        (lists.sort(), ...);
    }
    
    sortAll(a, b, c);
    

    You could also, I guess, write a helper to wrap your lists into an array of reference_wrapper to list<int> (since you can't have an array of references), but this is probably more confusing than helpful:

    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()