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

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

Leave a Comment