Video Coming Soon...
09: Using the fmt
Library
I find the C++ style of formatting strings using a cout
style operation to be...odd. It works, sure, but it's not how any other programming language does it. Other programming languages use something called a "format string" or possibly a "template" to craft strings from other data. In this exercise we'll use a nice library called fmt
to format strings.
The purpose of this exercise is two-fold:
- Learn how
fmt
works, which is also very similar to the C++std::format
with additional features. - Learn how to add other people's open source projects to your own project to get their features.
Starter Code
The first step in using fmt
is to #include
it, and then call fmt::print()
. Here's your usual starter code but with fmt
ready to go:
View Source file ex09a.cpp Only
#include <fmt/core.h>
#include <iostream>
using namespace std;
int main() {
fmt::print("Hello World!\n");
return 0;
}
Before this will work you need to configure your build to bring in the fmt
library's code. Type this up but remember compilation will fail until you add the library to your meson.build
.
Adding fmt
to Meson
To add fmt
to your meson.build
file by adding one line after the project()
part:
fmt = dependency('fmt')
Next you need to write your executable()
line to add fmt
as a dependency:
executable('ex09', 'ex09.cpp', dependencies: fmt)
This is the same as your other lines, but only adds dependencies: fmt
at the end. When you do this meson
will know you want to link (aka combine) your code with the fmt
library's code to create your ex09
executable.
The next thing you need to do is get meson
to download the fmt
library for you:
mkdir subprojects
meson wrap install fmt
After this the subprojects
directory will have the code for fmt
ready to use on this, and any other executable()
lines you write. Just add dependencies: fmt
to them and add the library to your code.
Using Static Linking
One small problem with our build is that it's currently configured to use "dynamic linking" but we need static linking. "Dynamic linking" is where your program doesn't actually contain the code for the fmt
library, instead it references a .so
, .dylib
, or .dll
file on your computer. Any time you run ex09
it will look in your program, see you are using the libfmt
library, and then "link it" in just as it runs.
The problem with dynamic linking is it requires your program knows where all these libraries are located. Normally this isn't a problem since there's a standard location for them, but when you're working on your programs these dynamic libraries are held deep inside the subprojects
directory, so your ex09
executable can't find it. The result is you try to run ex09
and either get an error, or on Windows it does nothing.
To test this, try compiling your ex09
executable and run it to see if you get an error. On Windows, try doing it this way:
meson compile -C builddir
start .\builddir\ex09
This is how you "double-click" on ex09
from inside Terminal. Now you should get a pop-up error saying that libfmt.dll
is missing. It is stupid that Microsoft doesn't print this message out in the Terminal, but hey, welcome to programming.
Static linking is where your compiler takes the code from the libfmt
library, and simply embeds all of the code you use into the your ex09
executable. The advantage of this is whenever you run your ex09
it doesn't have to find the libfmt
library anywhere as it's already there. We want static compilation for a lot of reasons, not just because it's easier to run it, but also because it makes debugging easier to do later.
To enable static linking you need to rebuild your builddir
with a new configuration. On OSX and Linux you would do this:
rm -rf builddir
meson setup --prefer-static --default-library=static builddir
On Windows it's the exact same meson setup
but the command to delete the builddir
directory is different:
rm -recurse -force builddir
meson setup --prefer-static --default-library=static builddir
Now recompile your code and you should see this:
meson compile -C builddir
./builddir/ex09
Hello World!
Using fmt
In Your Code
With your little Hello World
working we can finally explore what the fmt
library can do you for you. Here's an updated piece of code that demonstrates all the main features of fmt
:
View Source file ex09.cpp Only
#include <fmt/core.h>
#include <iostream>
#include <string>
using namespace std;
int main() {
int a_int = 1234;
long a_long = 37812394;
long long a_long_long = -68354647782938476;
float a_float = 0.1234;
double a_double = 3.23499;
fmt::println("Here's an int {}", a_int);
string msg = fmt::format(
"An int {}\n"
"A long {}\n"
"A long long {}\n"
"A float {}\n"
"A double {}\n",
a_int, a_long, a_long_long,
a_float, a_double);
fmt::println("The result is:");
fmt::print("{}", msg);
return 0;
}
As usual, get this to work a little at a time, but the large block in the center is a little tricky. I normally tell you to "type a little at a time" but if you did one line this code wouldn't compile. Let's focus on just that one block:
string msg = fmt::format(
"An int {}\n"
"A long {}\n"
"A long long {}\n"
"A float {}\n"
"A double {}\n",
a_int, a_long, a_long_long,
a_float, a_double);
This is actually "one line" of code, but obviously it's spread across multiple lines. If I convert this into a pattern it would be look like this:
string msg = fmt::format(<template>, <args>);
The <template>
is a string that uses {}
to say where format should "inject" one of the variables listed in <args>
. The <args>
are then a comma separated list of variables you defined earlier. How this becomes multiple lines is with a couple of tricks:
- Any time you have two or more strings next to each other they will be merged together. So you write
"Hello" "World"
it would be merged together into"HelloWorld"
. Yes, the space in the middle is skipped over and they're just combined together. - This combining of strings is something the compiler does, and it ignores all whitespace, so newlines and tabs are also skipped.
- This means you can make big strings formatted nicely in your code by just putting them on multiple lines.
- The next part that makes this work across multiple lines is how C++ processes the
<args>
. I am listing out the variables to use separated by,
. C++ knows I'm not done with the list yet because it hasn't seen the)
that ends the function call toformat()
. That means when it hits the end of the line it moves to the next line and keeps processing the<args>
until it reaches the)
that ends the function call.
Given all that, how are you supposed to type this in a little bit at a time? By only doing one <template>
and one <args>
at a time. For example, I would write:
string msg = fmt::format("An int {}\n", a_int);
That's one line and makes sure your format
call is working. Now we add the next piece and turn it into multiple lines:
string msg = fmt::format(
"An int {}\n" // just newline
"A long {}\n", // <-- move the comma
a_int, a_long);
See how I just write the next part of the format, but I move the ,
(comma) to the end of the next line? Once you have this working you can then add each part of the template, and each new argument for it, and complete the code.
Complete Code Breakdown
Once you get the code working we can review what's going on. I'm only going to cover the parts you don't already know from previous exercises:
1
- The only new include we have is to include
<fmt/core.h>
so we can use thefmt
library. 7-12
- This is very similar to the code from
ex08.cpp
but we aren't doing the conversions from strings. Instead I just write the numbers as constants directly. 14
- This is how you print a variable to the screen. This uses a new function
fmt::println()
that is similar tofmt::print()
, but it adds the\n
for you. It takes two arguments, one is atemplate
and the other is a list of variables. the list of variables can be as long as you want, separating each variable by,
(commas). Each place you write{}
in the template has to match with a variable after the template in order or else you'll get an error. 16
- I create a
string msg
instead of creating anostringstream
as inex08.cpp
. I then assign it to the result offmg::format(
which you should remember is a function call. This one calls the functionformat
in thefmt
namespace/library. You should also see that I end the line with(
which means I'm going to use multiple lines to finish this function call. 17-22
- These are each one line, and look like one string, but from the previous section you know that these are all merged together to make one giant string. Each one has a
{}
to place a variable, and a\n
to end the line in Terminal. 23-24
- This is the list of variables that we match to each
{}
in the above multi-line format string. Don't be fooled, this large multi-line format string is exactly the same as the one on line 14, it's just formatted so that you can see it as multiple lines in code to match the multiple lines in Terminal. 24
- At the end of line 24 I use
);
to finish the line of code, and this is matched up to the(
at the end of line 16. If you place your editor's cursor on this)
or the(
it should "match" them up and highlight the other one. If it doesn't then you may have an error in your code. 26
- I use another
fmt::println()
to print a message. 27
- Then I print out the
msg
variable by use a"{}"
format and only givingmsg
as the variable for it. This may cook your brain some but remember that the result of lines16-24
is to create a string that has been formatted already. All this print is doing is printing out that result. You could change this to usecout << msg;
for the same result.
More on Functions
These functions we're using from fmt
are special in a couple of ways:
- You are getting them not just from the
#include <fmt/core.h>
at the top, but also by changing yourmeson.build
to add thefmt
library. If you've never written code before this is a massive topic that we'll explore more, but think about them as "paid DLC for C++." You've already got thec++
compiler, you're just adding the DLC offmt
so you can get the new content. That content is new features and functions for your programs. - These functions also take variable numbers of arguments, which means you can add as many as you need to match your format templates. This is not normal though since most C++ code only uses functions that take one or two variables on average.
When you use these functions, I want you to think of them as transformers They take in a sequence of variables as arguments and transform them into a new result. Not all functions do this, but generally that's what a function is supposed to do. In this case, the fmt
library functions take a template and a list of variables, and transforms them into a formatted string or prints the formatted string to the Terminal.
Break It
As usual you should be trying to break this code to become familiar with the kinds of errors you get from the C++ compiler. C++ is notorious for having humongous convoluted error output so it's a good idea to cause them early. Some of these ideas will cause some insane error outputs:
- Give
fmt::print
too few or too many variables for the format. Which one gave you an error? - Call
fmt::print(msg)
directly instead offmt::print("{}", msg)
. This one's a bad one to read, but it's effectively saying that the first parameter tofmt::print
has to be a "constant expression", and you passed in a variable that's not constant. We'll get into that later, but just keep this in mind so you can recognize the error later when you see it. - Misspell something.
Try those then try as many other ways to cause errors. Bonus points if you can get this to crash in some way.
Further Study
- The final line
fmt::print("{}", msg)
is a little weird. It's a style choice to either always usefmt
, always usecout
, or to mix them. Try changing this line to usecout
and see if you like it. - Get rid of the
fmt::
on all the function calls. Do you remember what you need to do for that to work? - How does this compare to
ex08.cpp
? Which way of creating a formatted string do you like better? - Take the code from
ex08.cpp
and replicate its functionality usingfmt
from this exercise. - ADVANCED If you can use
fmt::format
to create strings from numbers, and you can use thestoi
style functions to convert strings to numbers, and every function returns a result, then can you get weird and havestoi
callfmt::format
to double convert a number? Don't do this in real code, but try to figure it out as an exercise. - ADVANCED Meson has a list of
wrap
libraries in their WrapDB list. Find one library and add it to yourmeson.build
to see if you can. I suggest that if you pick one that doesn't work to remove it and try another library that does work.
Register for Learn C++ the Hard Way
Register to gain access to additional videos which demonstrate each exercise. Videos are priced to cover the cost of hosting.