время жизни возвращаемого значения std :: initializer_list

Внедрение GCC уничтожаетstd::initializer_list массив, возвращаемый функцией в конце возвращаемого полного выражения. Это верно?

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

#include <initializer_list>
#include <iostream>

struct noisydt {
    ~noisydt() { std::cout << "destroyed\n"; }
};

void receive( std::initializer_list< noisydt > il ) {
    std::cout << "received\n";
}

std::initializer_list< noisydt > send() {
    return { {}, {}, {} };
}

int main() {
    receive( send() );
    std::initializer_list< noisydt > && il = send();
    receive( il );
}

Я думаю, что программа должна работать. Но основной стандарт немного запутан.

Оператор return инициализирует объект возвращаемого значения, как если бы он был объявлен

std::initializer_list< noisydt > ret = { {},{},{} };

Это инициализирует один временныйinitializer_list и его базовый массив хранения из данной серии инициализаторов, а затем инициализирует другойinitializer_list с первого. Каково время жизни массива? «Время жизни массива такое же, как уinitializer_list объект. "Но есть два из них; один является неоднозначным. Пример в 8.5.4 / 6, если он работает как объявлено, должен разрешить неоднозначность, что у массива есть время жизни скопированного объекта. Тогда возврат Массив значения также должен сохраняться в вызывающей функции, и должна быть возможность сохранить его, связав его с именованной ссылкой.

НаLWS, GCC ошибочно убивает массив перед возвратом, но сохраняет именованныйinitializer_list по примеру. Clang также правильно обрабатывает пример, но объекты в спискеникогда уничтожены; это может привести к утечке памяти. ICC не поддерживаетinitializer_list вообще.

Мой анализ правильный?

C ++ 11 §6.6.3 / 2:

Заявление о возврате сприготовился-INIT-лист инициализирует объект или ссылку, которые должны быть возвращены из функции путем копирования-инициализации списка (8.5.4) из указанного списка инициализатора.

8.5.4 / 1:

… Инициализация списка в контексте инициализации копирования называетсякопирование списка инициализация.

8.5 / 14:

Инициализация, которая происходит в формеT x = a; … называетсякопия инициализация.

Вернуться к 8.5.4 / 3:

Инициализация списка объекта или ссылки типа T определяется следующим образом:…

- В противном случае, если T является специализациейstd::initializer_list<E>,initializer_list Объект строится, как описано ниже, и используется для инициализации объекта в соответствии с правилами инициализации объекта из класса того же типа (8.5).

8.5.4 / 5:

Объект типаstd::initializer_list<E> построен из списка инициализатора, как если бы реализация выделяла массивN элементы типаE, гдеN количество элементов в списке инициализатора Каждый элемент этого массива инициализируется копией с соответствующим элементом списка инициализатора, иstd::initializer_list<E> Объект создается для ссылки на этот массив. Если для инициализации какого-либо элемента требуется сужающее преобразование, программа работает некорректно.

8.5.4 / 6:

Время жизни массива такое же, как у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 };
 }

Заv1 а такжеv2,initializer_list объект и массив, созданные для{ 1, 2, 3 } иметь полное выражение жизни. Заi3, объект initializer_list и массив имеют автоматическое время жизни.- конец примера]

Небольшое разъяснение о возврате списка в скобках

Когда вы возвращаете пустой список, заключенный в фигурные скобки,

Оператор return со списком фигурных скобок инициализирует объект или ссылку, которые должны быть возвращены из функции путем копирования-инициализации списка (8.5.4) из указанного списка инициализатора.

Это не означает, что объект, возвращаемый в область вызова, копируется из чего-либо. Например, это действительно:

struct nocopy {
    nocopy( int );
    nocopy( nocopy const & ) = delete;
    nocopy( nocopy && ) = delete;
};

nocopy f() {
    return { 3 };
}

это не:

nocopy f() {
    return nocopy{ 3 };
}

Copy-list-initialization просто означает эквивалент синтаксисаnocopy X = { 3 } используется для инициализации объекта, представляющего возвращаемое значение. Это не вызывает копию, и это оказывается идентичным примеру 8.5.4 / 6 продления времени жизни массива.

И Clang и GCC делаютсогласны по этому вопросу.

Другие заметки

ОбзорN2640 не упоминает об этом угловом случае. Здесь широко обсуждались отдельные особенности, но я ничего не вижу об их взаимодействии.

Реализация этого становится проблематичной, поскольку сводится к возврату необязательного массива переменной длины по значению. Посколькуstd::initializer_list не владеет своим содержимым, функция должна также возвращать что-то еще, что делает. При передаче в функцию это просто локальный массив фиксированного размера. Но в другом направлении VLA необходимо вернуть в стек вместе сstd::initializer_listУказатели. Затем необходимо сообщить вызывающей стороне, следует ли удалять последовательность (находятся ли они в стеке или нет).

На эту проблему очень легко наткнуться, возвращая список фигурных инициализаций из лямбда-функции как «естественный» способ вернуть несколько временных объектов, не заботясь о том, как они содержатся.

auto && il = []() -> std::initializer_list< noisydt >
               { return { noisydt{}, noisydt{} }; }();

Действительно, это похоже на то, как я сюда попал. Но было бы ошибкой опускать-> trailing-return-type, потому что вывод лямбда-типа возврата происходит только тогда, когда возвращается выражение, а список фигурных скобок не является выражением.

Ответы на вопрос(2)

Ваш ответ на вопрос