Never Let Your Guard Down in C++

Date: 2020-06-29

State: Done

I've been learning some C++ (inspired by a desire to write a Gemini server) and I find the language very pleasant to use. I've used Rust in the past, and the Rust community is fairly critical on C++, so I was surprised when I found the language easy to use and pleasantly safe (I use container types and managed pointers for everything.) Occasionally I find myself having to call into a C function, and I always wrap these accesses with STL or other managed types, and almost exhaustively prefer not having to call "new" or "delete", so that I deal with unsafeness in small portions. (Similar to how Rust corrals dangerous code in "unsafe" blocks and wraps them.)

C++20 has some great goodies, but in the interests of portability I am trying to stick to C++17. C++20 has some great functions for working with time, but C++17 has only a sparse time library. To generate ISO-8601 Datetimes, I needed to reach to "strftime". Rather than allocate a raw char* buffer and use a unique_ptr to it, I decided to allocate a string, have "strftime" read into it, then return the string. I used code like this:

  time_t time = chrono::system_clock::to_time_t(point);
  auto tm_time = gmtime(&time);
  // Initialize the string with a garbage character
  auto time_str = string('&', base_dt.size());
  auto bytes = strftime(time_str.data(), time_str.size() + 1, "%FT%TZ", tm_time);
  if (bytes <= 0) {
    return nullopt;
  }
  return make_optional(time_str);

Can you spot the error?

...

...

...

It's in this line:

auto time_str = string('&', base_dt.size())

I thought this would initialize "time_str" to be a string of size "base_dt.size()" filled with the '&' character, invoking the fill constructor. The fill constructor is based on passing in a "CharT*" or another "string". Because '&' is a character and _because base_dt.size() as a size_t can be converted to a "char"_ (ugh!), instead this constructor initializes the string to a length obtained by converting '&' into a size_t, with the character that "base_dt.size()" converts into. I found that out by diving into LLDB

LLDB (a close sibling to GDB)

but that's a post for another time.

I've learned to be careful when working with C++ semantics, especially silent casts.