Introduction to Boost Karma
Following up in the series about learning what’s in Boost, let’s get in Boost Karma, that generates strings in a very, very elaborate manner.
Too elaborate? Perhaps. But like most of the Boost libraries, even if you don’t end up using them in your production code, it’s still beneficial to know about them. They push the language to the limit of its boundaries, and are a source of interesting ideas to create our own abstractions and implementations.
Transcript of the video:
Hello, this is Jonathan Boccara for Fluent C++. Let’s continue learning what’s in the Boost library: today, we see Boost Karma.
Boost karma is a very very elaborate way to build strings in C++. It revolves around two concepts: generators and generating functions. Let’s see what that looks like in code.
To use Boost Karma, you can just include this path: #include <boost/spirit/include/karma.hpp>
(Boost Karma is part of a larger library called Boost Spirit, we may have a chance to get into that later).
#include <iostream> #include <string> #include <boost/spirit/include/karma.hpp> int main() { using boost::spirit::karma::int_; using boost::spirit::karma::generate; std::string result; generate( std::back_inserter(result), // the output int_, // the generator 42 // the input ); std::cout << result << "\n\n"; }
generate
is a generating function and int_
is a generator. Karma uses those generating functions that take an input (42
) and an output (std::back_inserter(result)
) and a generator that reads this input, does something with it, and puts the result into the output which is here a string.
int_
is perhaps the simplest generator. It takes the input and reads it as an int
. Let’s run this code… it prints:
42
That was an introductory example. Let’s see a more complex one.
#include <iostream> #include <string> #include <boost/spirit/include/karma.hpp> int main() { using boost::spirit::karma::int_; using boost::spirit::karma::generate; std::vector<int> numbers = {5, 3, 2}; std::string result; generate( std::back_inserter(result), // the output int_ << *(", " << int_), // the generator numbers // the input ); std::cout << result << "\n\n"; }
In this code we include the same header containing the code of Karma, and our input (numbers
) is a collection. Now our generator is slightly more complex that just reading an int.
This is how it reads: “read an int
“, and this <<
means in this context read “and then”. Meaning: “it reads an int
, and then…”.
And then there this *
. It’s very much like in regular expressions: it means “do it as many times as possible”. Do what? Do what follows. And what follows is ", " << int_
: print a comma followed by a space and then read an int
.
So it’s going to read an int
, and then read as many int
s as there are left in the collection, preceding them with a comma.
Output:
5, 3, 2
Let’s see one last example, that’s even more complex than that, just for the sake of showing that you can do arbitrarily elaborate expressions with Boost Karma:
#include <iostream> #include <string> #include <boost/spirit/include/karma.hpp> int main() { using namespace boost::spirit::karma; std::complex<double> c(3, -1); std::string result; generate( std::back_inserter(result), // the output !double_(0.0) << '(' << double_ << ',' << double_ << ')' // | // the generator omit[double_] << double_, // c.imag(), c.real(), c.imag() // the input ); std::cout << result << "\n\n"; }
Here the input is:
c.imag(), c.real(), c.imag()
A complex has a real part and an imaginary part. Here the generator takes three values: the imaginary part, then the real part, then the imaginary part again.
Why do we do that? Let’s have a look at the generator:
!double_(0.0) << '(' << double_ << ',' << double_ << ')' // | // the generator omit[double_] << double_,
The generator starts with:
!double_(0.0) << '(' << double_ << ',' << double_ << ')'
This represents a boolean expression meaning: “if this is not a double equal to 0.0”, then write a “(“, then take the next number (double) and then write a “,” and then the next number (double), and then “)”.
Comparing this with our input, this test will check if the imaginary part is not null, in which case we’ll print it out with the real part and imaginary part between brackets.
Running this code, it outputs:
(3.0,-1.0)
Now what if the condition is not true, which means that double_(0.0)
is true, which means that the first double
, the imaginary part, is null. In other terms we could re-write the example but with:
std::complex<double> c(3, 0);
So this is essentially a real number. In this case, we’re going to fall back into this |
, which means “else”. I know it doesn’t really look like “else”, but it’s what that means in Karma. We’re then falling back into this part of the generator:
omit[double_] << double_
This part means “omit the first double
(here, c.imag()
), and then print the next double (here, c.real()
).
Let’s run this code:
3.0
Note that we don’t get any bracket or commas, because we fell back into the second part of the generator.
I know how complex that looks, and to be honest I haven’t used it in real code at this stage. But I think it’s useful to know what’s in there, if anything for our culture, and to get inspiration to create our own libraries.
So this is the kind of things you can do with Boost Karma. There are other generators we haven’t seen and if you’re interested to know more about them you can go check out the official Boost documentation which is quite clear actually.
I hope this has been interesting to you, and that we’ll continue to learn Boost in other videos. If you liked this video and want to see more you can subscribe to the channel, and put a thumb up!
Thanks, and I see you next time.
Related posts:
- Introduction to Boost Phoenix
Share this post!