C++11: Any risk to moving const_cast elements out of a std::initializer_list?
This question builds on this @FredOverflow's question.
I've written a variadic template function that collapses any number of
arguments in a typed container. I use the type's constructor to convert
the variadic arguments into consumable values. E.g. _variant_t :)
I need this for my MySql C++ library when pushing arguments to prepared
statements in one strike, while my MySqlVariant converts the input data to
MYSQL_BINDs. As I may work with BLOBs, I'd like to avoid copy-construct as
much as possible when I can move&& the large containers around.
I've done a simple test and noticed that the initialize_list does
copy-construct for the stored elements and destroys them when it goes out
of scope. Perfect... Then I tried to move the data out of the
initializer_list and, to my surprise, it used lvalues not rvalues as I
expected with std::move.
Funny as this happens just after Going Native 2013 clearly warned me that
move does not move, forward does not forward... be like water, my friend -
to stay on the deep end of thinking.
But that did not stop me :) I decided to const_cast the initializer_list
values and still move them out. An eviction order needs to be enforced.
And this is my implementation:
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's
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))); //
<- THIS!
}
// done! :)
return aOutput;
}
Using it is easy:
// 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 :)
I've also uploaded a full test on IDEone ^ that shows as the memory of a
std::string moves properly with this function. (I would paste it all here
but it's slightly long...)
As long as the _variant_t (or whatever final wrapping object) has the
right constructors, it's great. And if the data can be moved out, it's
even better. And it pretty much works as I tested it and things std::move
in the right direction :)
My questions are simple:
Am I doing this right standard-wise?
Is the fact that it's working right intended or just a side effect?
If std::move does not work by default on initializer_list, is what I'm
doing here: illegal, immoral, hacky... or just plain wrong?
PS: I'm a self-taught Windows Native C++ developer, ignorant of the
standards.
^ my excuse if I'm doing really non-standard things here.
No comments:
Post a Comment