If by some miracle of search engine optimization you landed here first just googling around for a C++ code snippet or for some reason you found Techie Delight’s listicle 10 ways to convert a char to a string in C++ inadequately delightful, here’s your payoff right out of the gate.

std::string res{'c'};

Great! You’re free to go…

🔗 or: How I Learned that Braces and Parens Aren’t the Same Thing

… but, if you feel like sticking around for a brief debugging fable (perhaps even conveying something of a moral) you’re also welcome to stick around! :upside_down_face:

The story starts with @rodsan0 & I doing some pair programming on a command line argument parsing tool in our lab’s software library. One of the POSIX-y features we were working on was support for stringing together single-letter options (a la ls -lvd).

We found ourselves needing to grab the single-letter chars out of argument strings and then convert them back to strings to do lookups in a command de-aliasing map. “This should be an easy no-brainer thing to do,” we cheerfully assumed.

We poked through the first half of the first page of the Google results for “char to string C++.” The string fill constructor, which takes a repeat-count and a character as arguments, seemed a good fit. Bippity-boppity-boo… here’s what we typed up:

std::string res{1, 'c'};

The lookups weren’t working how we expected, so we tried printing out the lookup keys we were generating. They looked how we would expect, with

std::cout << res << std::endl;

yielding

c

Imagine our surprise 30 minutes later when we discovered that,

res != "c"

Digging in further, we discovered

res.size() == 2

Then, we found that the first character of the generated string was “ASCII Start of Header,” followed by the expected character c. Wut.

As these things do when you figure them out, the answer hit us with all of the subtlety of a Hefty bag filled with vegetable soup.

We quickly confirmed that

std::string res{97, 'c'} == "ac"

We weren’t using the fill constructor at all! We were using the initializer list constructor.

To get the fill constructor we expected, we should have been using parens

std::string res(1, 'c');

Simple enough, but how did we end up in this particular time-wasting pickle? Well, being a Good C++ Dilettante, somewhere along the way I had picked up the notion to just always throw braces on when creating new objects and feel like a good modern C++ good boy.

Wow. Much core guideline “ES.23: Prefer the {}-initializer syntax”. Good job me. After a few months of using braces in place of parens without incident I slid into the alluring notion that they were basically close enought the same thing so I could just not think about using braces.

But, as with all things C++, in ES.23 there is also a clearly documented…

Exception

For containers, there is a tradition for using {...} for a list of elements and (...) for sizes:

vector<int> v1(10);    // vector of 10 elements with the default value 0
vector<int> v2{10};    // vector of 1 element with the value 10

vector<int> v3(1, 2);  // vector of 1 element with the value 2
vector<int> v4{1, 2};  // vector of 2 element with the values 1 and 2

On the plus side, this rabbit hole introduced us to the little “11th Way” initializer list approach the post opened with!

🔗 Lesson of the Day

Anyways, here’s your “so-called promised fable:” doing things to Follow The Rule will only get you so far if you don’t also Understand The Rule.

As Kate Gregory eloquently points out, writing good C++ is an exercise in continually learning C++. In C++ and other (?) parts of life (??), keeping alert for things we do without really understanding should be a launching point to seek out understanding!

Also, braces and parens are not the same thing.

🔗 Let’s Chat

I would love to hear your thoughts, questions, and comments RE: char-to-string conversion & lifelong C++ learning!!!

I started a twitter thread (right below) so we can chat :phone: :phone: :phone: