Reputation: 69912
Inspired by this question I started wondering if there was a way to create an std::initializer_list
from a std::vector
.
Given that c++17 guarantees RVO, it seemed to me that it might be possible by building a compile-time dispatch table of initialisation functions.
Here's the first attempt at the code to do it:
#include <initializer_list>
#include <vector>
#include <iostream>
#include <array>
#include <stdexcept>
namespace impl
{
template<class T, std::size_t...Is>
auto as_init_list(std::vector<T> const& v, std::index_sequence<Is...>)
{
auto ret = std::initializer_list<T>
{
v[Is]...
};
return ret;
}
template<class T, std::size_t N>
auto as_init_list(std::vector<T> const& v)
{
auto ret = as_init_list(v, std::make_index_sequence<N>());
return ret;
}
template<class T, std::size_t...Is>
constexpr auto as_init_list_vtable(std::index_sequence<Is...>)
{
using ftype = std::initializer_list<T>(*)(std::vector<T> const&);
auto ret = std::array<ftype, sizeof...(Is)>
{{
&as_init_list<T, Is>...
}};
return ret;
}
template<class T, std::size_t N>
constexpr auto as_init_list_vtable()
{
auto ret = as_init_list_vtable<T>(std::make_index_sequence<N>());
return ret;
}
}
template<class T, std::size_t Limit = 100>
auto as_init_list(std::vector<T> const& vec)
-> std::initializer_list<T>
{
if (vec.size() >= Limit)
throw std::invalid_argument("too long");
static const auto table = impl::as_init_list_vtable<T, Limit>();
auto ret = table[vec.size()](vec);
return ret;
}
int main()
{
std::vector<int> v = { 1, 2, 3, 4 };
auto i = as_init_list(v);
for (auto&& x : i)
{
std::cout << x << '\n';
}
}
Of course, as half-expected, the output appears to be UB:
4200240
32765
0
0
http://coliru.stacked-crooked.com/a/1bf92111619317dd
In this (admittedly unusual and perverse case) I seem to have transgressed some rule around the lifetime of the elements of the items in the initializer_list, but at first glance it seems to me that the code should be valid (because of guaranteed RVO).
Am I right or wrong? Does the standard cover this scenario?
Upvotes: 5
Views: 1438
Reputation: 180955
In
auto as_init_list(std::vector<T> const& v, std::index_sequence<Is...>)
{
auto ret = std::initializer_list<T>
{
v[Is]...
};
return ret;
}
The array you build with { v[Is]... }
is a temporary object and and has it's lifetime bound to ret
. Once ret
goes out of scope the array is destroyed and you are left with a dangling std::initializer_list
. This is covered under [dcl.init.list]/6:
The array has the same lifetime as any other temporary object ([class.temporary]), except that initializing an
initializer_list
object from the array extends the lifetime of the array exactly like binding a reference to a temporary. [ Example:typedef std::complex<double> cmplx; std::vector<cmplx> v1 = { 1, 2, 3 }; void f() { std::vector<cmplx> v2{ 1, 2, 3 }; std::initializer_list<int> i3 = { 1, 2, 3 }; } struct A { std::initializer_list<int> i4; A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference };
For
v1
andv2
, the initializer_list object is a parameter in a function call, so the array created for{ 1, 2, 3 }
has full-expression lifetime. Fori3
, theinitializer_list
object is a variable, so the array persists for the lifetime of the variable. Fori4
, theinitializer_list
object is initialized in the constructor's ctor-initializer as if by binding a temporary array to a reference member, so the program is ill-formed ([class.base.init]). — end example ] [ Note: The implementation is free to allocate the array in read-only memory if an explicit array with the same initializer could be so allocated. — end note ]
emphasis mine
You would have to extend ret
's lifetime to the point where you use it in order to not have undefined behavior.
Upvotes: 7
Reputation: 93364
The following code produces a warning on gcc (trunk):
auto foo()
{
return std::initializer_list<int>{0, 1, 2};
}
warning: returning temporary initializer_list does not extend the lifetime of the underlying array [-Winit-list-lifetime] return std::initializer_list<int>{0, 1, 2}; ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This happens because std::initializer_list
very roughly behaves like a pair of pointers to an array created on the stack. The lifetime of the array is not tied to the lifetime of the std::initializer_list
instance.
Your as_init_list
function has the same issue. Marking ret
as static
works around the issue: http://coliru.stacked-crooked.com/a/e0ef17af8b398fb7
Upvotes: 6