84

Everyone creates std::vector from std::initializer_list, but what about the other way around?

eg. if you use a std::initializer_list as a parameter:

void someThing(std::initializer_list<int> items)
{
...
}

There are times when you have your items in a vector<T> instead of a literal list:

std::vector<int> v;
// populate v with values
someThing(v); // boom! No viable conversion etc.

The more general question is: how to create an stl::initializer_list from a STL iterable, not just std::vector.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Fil
  • 1,766
  • 1
  • 15
  • 15
  • I've wanted this too, except to pass a constant array instead of vector to my overload. It's pretty silly that `foo({1,2,3})` just works as expected, but `int f[] = {1,2,3}; foo(f);` does not; and needing to pass dual iterators is the very thing I want to avoid, since the std::initializer_list so neatly wraps both into one. At the least, I would expect an std::initializer_list constructor which accepted a const static array. – Dwayne Robinson Oct 27 '14 at 23:10
  • One useful use case for this is the following I have a method that returns a `vector` as a temporary object, and I want to create a `set` from it. If I could write `set foo{initializer_list(getVector())};` (or something similar), that would work. I can't write `set foo(getVector.begin(), getVector().end());`. (The return value is not stored and but calculated during the call.) So I need to copy the vector into a local object and use that for iterators. It's an extra move construction and more lines of code, and extra variables in scope to accidentally reference. – Troy Daniels Dec 17 '14 at 00:47
  • An initializer_list does not just wrap two iterators, an important detail is that the memory is contiguous. Now with a vector this is the case but as soon as you get the iterator, even if it's a random iterator, the contiguous aspect gets lost. See my answer below for what does work, but is kind of undocumented. – QBziZ Jul 10 '17 at 08:22

7 Answers7

41

The answer is NO, you cannot do that.

An object of type std::initializer_list<T> is a lightweight proxy object that provides access to an array of objects of type T. A std::initializer_list object is automatically constructed when:

  • a braced-init-list is used in list-initialization, including function-call list initialization and assignment expressions (not to be confused with constructor initializer lists)
  • a braced-init-list is bound to auto, including in a ranged for loop

As far as library support goes, std::initializer_list only has a default constructor that constructs an empty list, and its iterators are constant. The lack of a push_back() member means you cannot apply e.g. a std::copy with a std::back_inserter iterator adaptor to fill it, and neither can you assign through such iterators directly:

#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <vector>

int main() 
{
    auto v = std::vector<int> { 1, 2 };
    std::initializer_list<int> i;
    auto it = std::begin(i);
    *it = begin(v); // error: read-only variable is not assignable
}

Live Example

If you look at the Standard Containers, in addition to accepting std::initializer_list in their constructors / inserters, they all have constructors / inserters taking an iterator pair, and the implementation is likely to delegate the initializer_list function to the corresponding iterator pair function. E.g. the std::vector<T>::insert function in libc++ is this simple one-liner:

 iterator insert(const_iterator __position, initializer_list<value_type> __il)
        {return insert(__position, __il.begin(), __il.end());}

You should modify your code along similar lines:

void someThing(std::initializer_list<int> items)
{
    someThing(items.begin(), items.end()); // delegate
}

template<class It>
void someThing(It first, It last)
{
    for (auto it = first, it != last; ++it) // do your thing
}

In times when you have your items in a vector instead of a literal list:

std::vector<int> v = { 1, 2 };
auto i = { 1, 2 };
someThing(begin(v), end(v)); // OK
someThing(i); // also OK
someThing({1, 2}); // even better
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Yes I had come to the conclusion of passing dual iterators - I think I was confused about the equivalent of IEnumerable (from .NET world) in STL. There is no way to pass a single iterator, and know you've hit the end, right? – Fil Sep 22 '13 at 13:29
  • @Fil No that is not possible. [Ranges](http://www.open-std.org/pipermail/ranges/) are capable of detecting their ends. – TemplateRex Sep 22 '13 at 16:27
  • 1
    `std::initializer_list i(v.data(), v.data() + v.size());` appears to compile and be passable as a parameter. – Dwayne Robinson Dec 04 '13 at 22:36
  • 4
    @Dwayne, it can work (and probably will work on most compilers), but standard says that `initializer_list` has only default constructor. – magras Jun 15 '16 at 14:10
  • @DwayneRobinson, got the following error doing that: `error: 'constexpr std::initializer_list<_E>::initializer_list(std::initializer_list<_E>::const_iterator, std::initializer_list<_E>::size_type) [with _E = d2s::Json::FileRecord; std::initializer_list<_E>::const_iterator = const d2s::Json::FileRecord*; std::initializer_list<_E>::size_type = long unsigned int]' is private within this context` – Michael Jul 02 '19 at 19:34
  • @Michael Yes, per magras's comment, it appears it works on some compilers, but is not standard. – Dwayne Robinson Jul 03 '19 at 20:22
10

Apparently no, it is not possible. There is no such constructor (and I believe for good reasons), std::initializer_list is a weird creature.

What you could do instead is to change someThing() to accept a pair of iterators. In that way you get what you want, provided you can change the signature of that function (it isn't in a third party library, etc).

Ali
  • 56,466
  • 29
  • 168
  • 265
5

Yes you can do this, but you don't want to do it, because how you have to do it is pretty silly.

First, determine what the max length of your list is. There must be a max length, because size_t is not unbounded. Ideally find a better (smaller) one, like 10.

Second, write magic switch code that takes a run-time integer, and maps it to a compile time integer, and then invokes a template class or function with that compile time integer. Such code needs a max integer size -- use the max length above.

Now, magic switch the size of the vector into a compile time length.

Create a compile time sequence of integers, from 0 to length-1. Unpack that sequence in a initializer_list construction, each time invoking [] on the std::vector. Call your function with that resulting initializer_list.

The above is tricky and ridiculous and most compilers will blow up on it. There is one step I'm uncertain of the legality of -- is the construction of an initializer_list a legal spot to do varardic argument unpacking?

Here is an example of a magic switch: Can I separate creation and usage locations of compile-time strategies?

Here is an example of the indices, or sequence, trick: Constructor arguments from tuple

This post should only be of theoretical interest, because practically this is a really silly way to solve this problem.

Doing it with an arbitrary iterable is harder, without doing n^2 work. But as the above is already ridiculous enough, and the arbitrary iterable version would be more ridiculous... (Maybe with a pack of lambdas -- getting it so that the arguments are evaluated in order could be tricky. Is there a sequence point between the evaluation of the various arguments to an initializer list?)

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • It seems no-one would ask for a `std::initializer_list` in a library, but use a dual iterator instead. Fair enough :) – Fil Sep 22 '13 at 13:31
2

I posted a way that seemed to work but unfortunately caused memory access violations because of how initializer_lists are treated as references to locally-scoped copies of values.

Here's an alternative. A separate function and a separate static initializer list is generated for each possible number of items, which are counted with a parameter pack. This is not thread safe and uses a const_cast (which is considered very bad) to write into the static initializer_list memory. However, it works cleanly in both gcc and clang.

If for some obscure reason you need this problem solved and have no other options, you could try this hack.

#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <type_traits>
#include <vector>

namespace __range_to_initializer_list {

    constexpr size_t DEFAULT_MAX_LENGTH = 128;

    template <typename V> struct backingValue { static V value; };
    template <typename V> V backingValue<V>::value;

    template <typename V, typename... Vcount> struct backingList { static std::initializer_list<V> list; };
    template <typename V, typename... Vcount>
    std::initializer_list<V> backingList<V, Vcount...>::list = {(Vcount)backingValue<V>::value...};

    template <size_t maxLength, typename It, typename V = typename It::value_type, typename... Vcount>
    static typename std::enable_if< sizeof...(Vcount) >= maxLength,
    std::initializer_list<V> >::type generate_n(It begin, It end, It current)
    {
        throw std::length_error("More than maxLength elements in range.");
    }

    template <size_t maxLength = DEFAULT_MAX_LENGTH, typename It, typename V = typename It::value_type, typename... Vcount>
    static typename std::enable_if< sizeof...(Vcount) < maxLength,
    std::initializer_list<V> >::type generate_n(It begin, It end, It current)
    {
        if (current != end)
            return generate_n<maxLength, It, V, V, Vcount...>(begin, end, ++current);

        current = begin;
        for (auto it = backingList<V,Vcount...>::list.begin();
             it != backingList<V,Vcount...>::list.end();
             ++current, ++it)
            *const_cast<V*>(&*it) = *current;

        return backingList<V,Vcount...>::list;
    }

}

template <typename It>
std::initializer_list<typename It::value_type> range_to_initializer_list(It begin, It end)
{
    return __range_to_initializer_list::generate_n(begin, end, begin);
}

int main()
{
    std::vector<int> vec = {1,2,3,4,5,6,7,8,9,10};
    std::initializer_list<int> list = range_to_initializer_list(vec.begin(), vec.end());
    for (int i : list)
        std::cout << i << std::endl;
    return 0;
}
fuzzyTew
  • 3,511
  • 29
  • 24
  • 1
    [**Does not work for me**](http://coliru.stacked-crooked.com/a/6a29f0b4057334b0), although I am unsure what kind of undefined behavior you are relying on. – TemplateRex Sep 02 '15 at 19:48
  • 1
    This is probably https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56568 . clang doesn't like this either. I don't think we should be returning initializer_lists from functions for now. – fuzzyTew Sep 03 '15 at 21:44
  • Updated to work around restrictions of memory & scope. – fuzzyTew Sep 04 '15 at 04:23
  • The name `__range_to_initializer_list` is reserved to the language implementation. – eerorika May 18 '21 at 12:04
  • given this mutates the contents of a compile-time construct, the use of the reserved name is appropriate. how or whether this functions is technically implementation-defined. It would be better to mix this idea with Yakk's idea, generating the initializer list from references. – fuzzyTew May 18 '21 at 17:38
0

If you don't mind copies, then I think something like this would work:

template<class Iterator>
using iterator_init_list = std::initializer_list<typename std::iterator_traits<Iterator>::value_type>;

template<class Iterator, class... Ts>
iterator_init_list<Iterator> to_initializer_list(Iterator start, Iterator last, Ts... xs)
{
    if (start == last) return iterator_init_list<Iterator>{xs...};
    else return to_initializer_list(start+1, last, xs..., *start);
}
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
  • Interesting idea, but unfortunately I'm pretty sure this is undefined behaviour. initializer_lists are not generic containers that can be returned from functions; their lifetime is scoped in the same way that local variables are, and there is no guarantee that the objects in the initializer_list will persist outside the call (https://stackoverflow.com/a/17635374/1678770) – Human-Compiler Nov 16 '17 at 18:49
0

I think that the best solution without templating obscure iterator classes for the good of passing vector using its two methods returning iterators is just to implement your function logic in a function taking vector.

void someThing(std::initializer_list<int> items)
{
     std::vector<int> v;
     for(int i:items)
     {
             v.push_back(i);
     }
     someThing(v);
}

void someThing(std::vector<int> items)
{
...
}
VojtaK
  • 483
  • 4
  • 13
-1
std::vector<int> v;
someThing(std::initializer_list<int>(&v.front(), &v.front() + v.size()));