Построение std :: initializer_list динамического размера, часть II - PullRequest
0 голосов
/ 05 сентября 2018

Вдохновленный этим вопросом Мне стало интересно, есть ли способ создать 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 / а / 1bf92111619317dd

В этом (по общему признанию, необычном и извращенном случае) я, кажется, нарушил какое-то правило вокруг времени жизни элементов элементов в initializer_list, но на первый взгляд мне кажется, что код должен быть действительным (из-за гарантированного РВО).

Я прав или нет? Стандарт охватывает этот сценарий?

Ответы [ 2 ]

0 голосов
/ 05 сентября 2018

В

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.teilitary]), за исключением того, что инициализация объекта 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 до точки, в которой вы его используете, чтобы не иметь неопределенного поведения.

0 голосов
/ 05 сентября 2018

Следующий код выдает предупреждение на 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};
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

живой пример на wandbox.org


Это происходит потому, что std::initializer_list очень грубо ведет себя как пара указателей на массив, созданный в стеке. Время жизни массива не привязано к времени жизни экземпляра std::initializer_list.

Ваша функция as_init_list имеет ту же проблему. Пометка ret как static решает проблему: http://coliru.stacked -crooked.com / a / e0ef17af8b398fb7

...