I Just Love (♥!) C++

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.

About these ads
This entry was posted in Think Tank and tagged , , , . Bookmark the permalink.

2 Responses to I Just Love (♥!) C++

  1. 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’});

    • lueinc says:

      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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s