构造动态尺寸的std :: initializer_list,第二部分

时间:2018-09-05 13:53:44

标签: c++ vector language-lawyer c++17

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

我是对还是错?该标准涵盖这种情况吗?

2 个答案:

答案 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};
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

live example on wandbox.org


之所以会这样,是因为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
};
     

对于v1v2,initializer_­list对象是函数调用中的参数,因此为{ 1, 2, 3 }创建的数组具有完整的表达式寿命。 对于i3initializer_­list对象是一个变量,因此数组在变量的生存期内一直存在。对于i4initializer_­list对象在构造函数的 ctor-initializer 中初始化,就像通过将临时数组绑定到引用成员上一样,因此程序格式不正确([[ class.base.init])。注释示例:如果可以这样分配具有相同初始化程序的显式数组,则可以在只读存储器中分配该数组。 —尾注]

强调我的

您必须将ret的生存期延长到使用它的程度,以免出现不确定的行为。