受this question的启发,我开始怀疑是否有一种方法可以从std::initializer_list
创建std::vector
。
鉴于c ++ 17可以保证RVO,在我看来,通过构建初始化函数的编译时分派表可以实现这一点。
这是首次尝试执行此操作的代码:
#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';
}
}
当然,正如预期的那样,输出似乎是UB:
4200240
32765
0
0
http://coliru.stacked-crooked.com/a/1bf92111619317dd
在这种情况下(公认的不正常和不正常的情况),我似乎在initializer_list中的元素生命周期内违反了一些规则,但是乍一看,我觉得代码应该是有效的(因为保证了RVO)。
我是对还是错?该标准涵盖这种情况吗?
答案 0 :(得分:6)
以下代码在 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}; ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
之所以会这样,是因为std::initializer_list
的行为非常类似于一对指向在堆栈上创建的数组的指针。数组的生存期与std::initializer_list
实例的生存期无关。
您的as_init_list
函数具有相同的问题。将ret
标记为static
可以解决以下问题:http://coliru.stacked-crooked.com/a/e0ef17af8b398fb7
答案 1 :(得分:4)
在
auto as_init_list(std::vector<T> const& v, std::index_sequence<Is...>)
{
auto ret = std::initializer_list<T>
{
v[Is]...
};
return ret;
}
使用{ v[Is]... }
构建的数组是一个临时对象,并且生命周期绑定到ret
。一旦ret
超出范围,该阵列将被销毁,并且将剩下一个悬垂的std::initializer_list
。 [dcl.init.list]/6对此进行了介绍:
该数组具有与任何其他临时对象([class.temporary])相同的生存期,不同之处在于,从数组初始化
initializer_list
对象可以延长数组的生存期,就像将引用绑定到临时对象一样。 [示例: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 };
对于
v1
和v2
,initializer_list对象是函数调用中的参数,因此为{ 1, 2, 3 }
创建的数组具有完整的表达式寿命。 对于i3
,initializer_list
对象是一个变量,因此数组在变量的生存期内一直存在。对于i4
,initializer_list
对象在构造函数的 ctor-initializer 中初始化,就像通过将临时数组绑定到引用成员上一样,因此程序格式不正确([[ class.base.init])。注释示例:如果可以这样分配具有相同初始化程序的显式数组,则可以在只读存储器中分配该数组。 —尾注]
强调我的
您必须将ret
的生存期延长到使用它的程度,以免出现不确定的行为。