Let’s say you want to keep a bunch byte values around in a std::vector<uint8_t> until you get the chance to write it to a file. So, here comes the first piece of data:
std::vector<uint8_t> foo;
foo.push_back("bar");
Anyone who knows their C++ knows that won’t work, I’m merely demonstrating what I want to do at this point: put three bytes at the end of the vector, which happen to make up a magic string in the final file. So, how about that insert? It puts multiple values in a vector, right?
foo.insert(foo.end(), std::vector((uint8_t*)"foo"));
Nah, insert only takes iterators. Since I don’t want to define roughly 15–20 variables to insert all the values I need, I’ll let a lambda take care of it for me.
auto pushmany = [&](uint8_t * bytes) {
foo.insert(foo.end(), std::begin(bytes), std::end(bytes));
}
pushmany((uint8_t*)"foo");
But of course not:
foo.cpp:42:42: error: no matching function for call to ‘begin(uint8_t*&)’
Ooooook then, how about I just loop through the array?
auto pushmany = [&](uint8_t * bytes) {
for (auto & i : bytes) {
foo.push_back(i);
}
}
Results?
foo.cpp:42:42: error: no matching function for call to ‘begin(uint8_t*&)’
Since so far I’m stuck on a string, how’s about I just use a char* array here?
auto pushmany = [&](char * bytes) {
for (auto & i : bytes) {
foo.push_back(i);
}
}
pushmany("foo");
Survey says…
foo.cpp:42:42: error: invalid range expression of type 'char *'; no viable 'begin' function available
(I’ve switched from g++ to clang++ at this point to have more readable walls of errors.)
How about a std::vector to the lambda? Let me know how you manage to transform a string literal into a std::vector<uint8_t>; I’d love to know.
So, new approach:
foo = {(uint8_t*)"foo", (uint8_t*)"bar", ...}
The answer:
note: candidate function not viable: no known conversion from 'uint8_t *' (aka 'unsigned char *') to 'const value_type'
(aka 'const unsigned char') for 1st argument; dereference the argument with *
Oh?
foo = {*(uint8_t*)"foo", ...}
And it compiles! It works! Or… I think. Maybe. Y’see, this finally, finally compiles, but the output file from all this was far shorter than it should’ve been1, so at this point my patience ran out and I git reset --hard.
This was me trying to make some code of mine more C++ (in addition to factoring it out into its own function), avoiding the use of char arrays when possible (frustratingly, fstream binary reads make this unavoidable to a point). Here’s the kicker: beforehand, I didn’t bother collecting it all into a container, because it was all inlined, so the equivalent line I was trying to duplicate there was this:
std::ofstream thefile(...);
thefile.write("foo", 3);
So you see, at least parts of the standard library have the capability of storing multiple units of something into a class at once. But pity be to he who dares desire the same thing of his std::vector.
I realize I could’ve just declared a char * variable and then do an insert using std::begin and std::end (even though I’m sure those weren’t working, somehow). I could’ve looked up the numbers of each character in the string and typed out a bunch of push_back statements. I realize I probably made a stupid error in this (somewhat abridged) journey just now that made me think something that would’ve worked didn’t.
The point is I shouldn’t have to do any of this. This has always been a sticking point with C++ for me; oftentimes things are frustratingly absent from the standard library, or are incomprehensibly difficult to accomplish. I used to feel like this all the time when I started using C++, and recently I thought I had grown past that. That I was comfortable with the fact that C++ makes some things more verbose, it wasn’t too much of a bother.
Then something like this happens.
Dear C++, adding more than one element to a list (be it a vector or deque or …) is a simple thing. It should be easy to accomplish. Why can’t push_back accept a same-typed vector? At least then my frustration would be focused on how I couldn’t convert "foo" into a std::vector<uint8_t> (which wasn’t my primary issue during this, so I’m sure the solution is simple, and that I simply didn’t spend enough time on the problem to find it).
C++, here’s how Perl 6 does this2:
my @a;
@a.push("foo".encode.list);
Magic.
1And I just now realized why this probably occurred. I was really frustrated by this point, I had no interest in solving the problem further. So small wonder I didn’t see it sooner.
2The .encode.list instead of .ords is just to replicate C++’s not-handling-Unicode default.
Insert doesn’t only take iterators. It has a couple of overloads, see http://en.cppreference.com/w/cpp/container/vector/insert, specifically number 5 for more information.
To cut things short, basically you need something like:
foo.insert (foo.end (), {‘a’, ‘b’, ‘c’, ‘d’});
Ah. See, when I was looking around on cppreference during this, I clicked on std::iterator_list just to make sure I knew how to use them, and I got the idea that I had to explicitly write out std::iterator_list in that overload, which I couldn’t get working properly.
So nice to see this one’s chalked up to the “stupid mistake” category 🙂 . Would’ve been annoying, but doable.