Могу ли я перемещать элементы из std::initializer_list<T>
?
#include <initializer_list>
#include <utility>
template<typename T>
void foo(std::initializer_list<T> list)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
bar(std::move(*it)); // kosher?
}
}
Поскольку std::intializer_list<T>
требует особого внимания компилятора и не имеет семантики значений, как обычные контейнеры стандартной библиотеки C ++, я бы предпочел перестраховаться, чем извиниться и спросить.
c++
templates
c++11
move-semantics
initializer-list
fredoverflow
источник
источник
initializer_list<T>
являются не -const. Нравится,initializer_list<int>
относится кint
объектам. Но я думаю, что это дефект - предполагается, что компиляторы могут статически размещать список в памяти только для чтения.Ответы:
Нет, это не сработает так, как задумано; вы все равно получите копии. Я очень удивлен этим, так как я думал, что они
initializer_list
существовали для хранения множества временных файлов, пока они не былиmove
созданы.begin
иend
дляinitializer_list
возвратаconst T *
, поэтому результатmove
в вашем кодеT const &&
- неизменная ссылка rvalue. От такого выражения невозможно избавиться. Он будет привязан к параметру функции типа,T const &
потому что rvalue действительно привязывается к ссылкам const lvalue, и вы по-прежнему будете видеть семантику копирования.Вероятно, причина этого в том, что компилятор может выбрать
initializer_list
статически инициализированную константу, но кажется, что было бы чище сделать ее типinitializer_list
илиconst initializer_list
по усмотрению компилятора, поэтому пользователь не знает, ожидатьconst
ли результатbegin
иend
. Но это всего лишь мое чутье, наверное, есть веская причина, по которой я ошибаюсь.Обновление: я написал предложение ISO для
initializer_list
поддержки типов, предназначенных только для перемещения. Это только первый черновик, и он еще нигде не реализован, но вы можете увидеть его для более подробного анализа проблемы.источник
std::move
безопасно, если не продуктивно. (За исключениемT const&&
конструкторов ходов .)const std::initializer_list<T>
или простоstd::initializer_list<T>
так, чтобы не вызывать неожиданностей достаточно часто. Учтите, что каждый аргумент в классеinitializer_list
может быть любымconst
или нет, и это известно в контексте вызывающего, но компилятор должен сгенерировать только одну версию кода в контексте вызываемого (т.е. внутриfoo
он ничего не знает об аргументах что звонящий проходит)std::initializer_list &&
перегрузка что-то делала, даже если также требуется перегрузка без ссылки. Полагаю, это было бы еще более запутанным, чем нынешняя ситуация, которая и так плоха.Не так, как вы намеревались. Вы не можете переместить
const
объект. Иstd::initializer_list
только предоставляетconst
доступ к своим элементам. Так что типit
естьconst T *
.Ваша попытка позвонить
std::move(*it)
приведет только к l-значению. IE: копия.std::initializer_list
ссылается на статическую память. Вот для чего нужен класс. Вы не можете двигаться из статической памяти, потому что движение подразумевает ее изменение. С него можно только копировать.источник
initializer_list
ссылается на стек, если это необходимо. (Если содержимое непостоянно, оно по-прежнему является потокобезопасным.)initializer_list
объект может быть xvalue, но его содержимое (фактический массив значений, на которые он указывает) являетсяconst
, потому что это содержимое может быть статическими значениями. Вы просто не можете перейти от содержимого файлаinitializer_list
.const
получив xvalue.move
может быть бессмысленным, но это законно и даже возможно объявить параметр, который принимает именно это. Если перемещение определенного типа невозможно, оно может даже работать правильно.std::move
. Это гарантирует, что при проверке вы сможете определить, когда происходит операция перемещения, поскольку она влияет как на источник, так и на место назначения (вы не хотите, чтобы это происходило неявно для именованных объектов). Из-за этого, если вы используетеstd::move
в месте, где операция перемещения не происходит (и фактического перемещения не произойдет, если у вас есть значениеconst
x), тогда код вводит в заблуждение. Я считаю ошибкойstd::move
быть вызванным кconst
объекту.const &&
классом, собирающим мусор, с управляемыми указателями, где все относящееся к делу изменялось, а перемещение перемещало управление указателем, но не влияло на содержащееся значение. Всегда есть сложные крайние случаи: v).Это не будет работать, как указано, потому что
list.begin()
имеет типconst T *
, и вы не можете перейти от постоянного объекта. Разработчики языка, вероятно, сделали это так, чтобы позволить спискам инициализаторов содержать, например, строковые константы, из которых было бы неуместно перемещаться.Однако, если вы находитесь в ситуации, когда вы знаете, что список инициализаторов содержит выражения rvalue (или вы хотите заставить пользователя написать их), тогда есть трюк, который заставит его работать (меня вдохновил ответ Суманта для это, но решение намного проще, чем это). Вам нужно, чтобы элементы, хранящиеся в списке инициализаторов, были не
T
значениями, а значениями, которые инкапсулируютT&&
. Тогда, даже если сами эти значенияconst
квалифицированы, они все равно могут получить изменяемое rvalue.Теперь вместо объявления
initializer_list<T>
аргумента вы объявляетеinitializer_list<rref_capture<T> >
аргумент. Вот конкретный пример, включающий векторstd::unique_ptr<int>
интеллектуальных указателей, для которого определена только семантика перемещения (поэтому сами эти объекты никогда не могут быть сохранены в списке инициализаторов); однако список инициализаторов ниже компилируется без проблем.На один вопрос нужен ответ: если элементы списка инициализатора должны быть истинными значениями pr (в данном примере это значения x), гарантирует ли язык, что время жизни соответствующих временных файлов продлится до точки, в которой они используются? Честно говоря, я не думаю, что соответствующий раздел 8.5 стандарта вообще решает эту проблему. Однако, читая 1.9: 10, может показаться, что соответствующее полное выражение во всех случаях включает использование списка инициализаторов, поэтому я думаю, что нет опасности висящих ссылок на rvalue.
источник
"Hello world"
? Если вы двигаетесь от них, вы просто копируете указатель (или привязываете ссылку).{..}
привязаны к ссылкам в параметре функцииrref_capture
. Это не продлевает их срок службы, они все равно уничтожаются в конце полного выражения, в котором они были созданы.std::initializer_list<rref_capture<T>>
в какой-нибудь признак преобразования по вашему выбору, например,std::decay_t
чтобы заблокировать нежелательный вывод.Я подумал, что было бы поучительно предложить разумную отправную точку для обходного пути.
Комментарии встроены.
источник
initializer_list
можно ли переместить, а не в том, есть ли у кого-нибудь обходные пути. Кроме того, главное преимуществоinitializer_list
заключается в том, что шаблон основан только на типе элемента, а не на количестве элементов, и, следовательно, не требует, чтобы получатели также были в шаблоне - и это полностью теряет это.initializer_list
но не подпадают под все ограничения, которые делают это полезным. :)initializer_list
(с помощью магии компилятора) избегает необходимости шаблонных функций по количеству элементов, что по своей сути требуется для альтернатив, основанных на массивах и / или вариативных функциях, тем самым ограничивая диапазон случаев, когда последние могут использоваться. Насколько я понимаю, это как раз одна из основных причин наличияinitializer_list
, поэтому, казалось, стоит упомянуть.Кажется, это не разрешено в текущем стандарте, как уже было сказано . Вот еще один обходной путь для достижения чего-то подобного, путем определения функции как переменной вместо использования списка инициализаторов.
Шаблоны Variadic могут соответствующим образом обрабатывать ссылки на r-значения, в отличие от initializer_list.
В этом примере кода я использовал набор небольших вспомогательных функций для преобразования вариативных аргументов в вектор, чтобы сделать его похожим на исходный код. Но, конечно, вместо этого вы можете написать рекурсивную функцию с вариативными шаблонами.
источник
initializer_list
можно ли переместить, а не в том, есть ли у кого-нибудь обходные пути. Кроме того, главное преимуществоinitializer_list
заключается в том, что шаблон основан только на типе элемента, а не на количестве элементов, и, следовательно, не требует, чтобы получатели также были в шаблоне - и это полностью теряет это.У меня есть гораздо более простая реализация, в которой используется класс-оболочка, который действует как тег, отмечающий намерение перемещения элементов. Это стоимость времени компиляции.
Класс-оболочка предназначен для использования в том виде, в котором
std::move
он используется, просто замените егоstd::move
наmove_wrapper
, но для этого требуется C ++ 17. Для более старых спецификаций вы можете использовать дополнительный метод построения.Вам нужно будет написать методы / конструкторы построителя, которые принимают классы-оболочки внутри
initializer_list
и соответственно перемещают элементы.Если вам нужно скопировать некоторые элементы, а не перемещать, создайте копию, прежде чем передавать ее
initializer_list
.Код должен быть самодокументированным.
источник
Вместо использования a
std::initializer_list<T>
вы можете объявить свой аргумент как ссылку на массив rvalue:См. Пример использования
std::unique_ptr<int>
: https://gcc.godbolt.org/z/2uNxv6источник
Рассмотрим
in<T>
идиому, описанную на cpptruths . Идея состоит в том, чтобы определить lvalue / rvalue во время выполнения, а затем вызвать перемещение или копирование.in<T>
обнаружит rvalue / lvalue, даже если стандартный интерфейс, предоставляемый initializer_list, является константной ссылкой.источник