Этот вопрос основывается на этом вопросе @FredOverflow.
ПОДТВЕРЖДЕНИЕ:
initializer_list
требуется, поскольку VС++ 2012 имеет ошибку, предотвращает пересылку расширение аргументов с именами._MSC_VER <= 1700
имеет ошибку.
Я написал вариационную функцию шаблона, которая сворачивает любое количество аргументов в типизированном контейнере. Я использую конструктор типа для преобразования вариационных аргументов в расходные значения. Например. _variant_t
:)
Мне нужно это для моей библиотеки MySql
С++ при нажатии аргументов готовых операторов в один удар, а мой MySqlVariant
преобразует входные данные в MYSQL_BIND
s. Поскольку я могу работать с BLOB
s, я бы хотел как можно больше избежать копирования-конструкции, когда могу move&&
использовать большие контейнеры.
Я проделал простой тест и заметил, что initialize_list
выполняет copy-construct
для сохраненных элементов и уничтожает их, когда он выходит из области видимости. Perfect... Затем я попытался вывести данные из initializer_list
, и, к моему удивлению, он использовал lvalues
not rvalues
, как я ожидал, с помощью std::move
.
Забавно, как это происходит сразу после Going Native 2013 ясно предупредил меня, что перемещение не перемещается, вперед не вперед... быть как вода, мой друг - оставаться на глубоком конце мышления.
Но это меня не остановило:) Я решил const_cast
значения initializer_list
и по-прежнему их вытаскивать. Необходимо обеспечить соблюдение порядка выселения. И это моя реализация:
template <typename Output_t, typename ...Input_t>
inline Output_t& Compact(Output_t& aOutput, Input_t&& ...aInput){
// should I do this? makes sense...
if(!sizeof...(aInput)){
return aOutput;
}
// I like typedefs as they shorten the code :)
typedef Output_t::value_type Type_t;
// can be either lvalues or rvalues in the initializer_list when it populated.
std::initializer_list<Type_t> vInput = { std::forward<Input_t>(aInput)... };
// now move the initializer_list into the vector.
aOutput.reserve(aOutput.size() + vInput.size());
for(auto vIter(vInput.begin()), vEnd(vInput.end()); vIter != vEnd; ++vIter){
// move (don't copy) out the lvalue or rvalue out of the initializer_list.
// aOutput.emplace_back(std::move(const_cast<Type_t&>(*vIter))); // <- BAD!
// the answer points out that the above is undefined so, use the below
aOutput.emplace_back(*vIter); // <- THIS is STANDARD LEGAL (copy ctor)!
}
// done! :)
return aOutput;
}
Использовать его легко:
// You need to pre-declare the container as you could use a vector or a list...
// as long as .emplace_back is on duty!
std::vector<MySqlVariant> vParams;
Compact(vParams, 1, 1.5, 1.6F, "string", L"wstring",
std::move(aBlob), aSystemTime); // MySql params :)
Я также загрузил полный тест на IDEone ^, который показывает поскольку память о std::string
корректно перемещается с этой функцией. (Я бы вставлял все это здесь, но немного длиннее...)
Пока _variant_t
(или какой-либо конечный оберточный объект) имеет правильные конструкторы, это здорово. И если данные могут быть перемещены, это еще лучше. И это в значительной степени работает, когда я тестировал его и вещи std::move
в правильном направлении:)
Мои вопросы просты:
- Я делаю это правильно стандартно?
- Является ли тот факт, что он работает правильно, или просто побочный эффект?
- Если
std::move
не работает по умолчанию наinitializer_list
, это то, что я здесь делаю: незаконный, аморальный, хакерский... или просто неправильно?
PS. Я разработчик Windows Native C++
, не осведомленный о стандартах.
^ Извините, если я делаю действительно нестандартные вещи здесь.
UPDATE
Спасибо всем, у меня есть как ответ, так и решение (короткое и длинное).
И я люблю С++ 11 сторону SO. Многие знающие люди здесь...