This question builds on this @FredOverflow's question.
CLARIFICATION:
initializer_list
approach is required as the VC++2012 has a bug the prevents forwarded expansion of namespaced arguments._MSC_VER <= 1700
has the bug.
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_BIND
s. As I may work with BLOB
s, 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))); // <- 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;
}
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 oninitializer_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.
UPDATE
Thanks everyone, I have both the answer and the solution (a short and long one) now.
And I love the C++11 side of SO. Many knowledgeable people here...