tinkering with c++ — boost header file only programming

I’m presenting a C++ application that uses the header-file-only Boost libraries, in this case Multiprecision. The link to the Boost documentation for this library is at the bottom of this post.

#include <algorithm>#include <iostream>#include <iomanip>#include <stdexcept>#include <filesystem>#include <string>#include <boost/multiprecision/cpp_int.hpp>using std::cout;using std::cerr;using std::left;using std::right;using std::setw;using std::fixed;using std::stoi;using std::string;using std::filesystem::path;using std::invalid_argument;using boost::multiprecision::cpp_int;void factorial(cpp_int n) {cpp_int a = 1;cout << right << setw(4) << fixed << n;while ( n > 0 ) {a = a * n;n--;}cout << left << "! = " << a << "\n";}int main(int argc, char *argv[]) {string appname = path(argv[0]).stem();if (argc == 1) {cout << appname << " can be invoked with one or more integer arguments.\n";cout << "Example: " << appname << " 10 20 30 40 50 60 70 80 90 100\n";}int index{1};try {for (; index < argc; factorial(stoi(argv[index++])));}catch(invalid_argument ia) {index--;cerr << apname << " invalid argument: " << argv[index] << "\n";return -1;}return 0;}

General Description

You’ve seen this code before, but in a far simpler code format than this. This version of the application has a try/catch block at lines 43 and 46 to catch an exception that stoi will throw if it’s given a non-numeric argument to parse. The catch statement explicitly catches an invalid argument and will print out what caused the application to stop. This is a lot better than the core dump it performed before this. Furthermore, if you call this application without any arguments it will give you several lines of help (at least, I hope they’re helpful). I’ve pulled a little bit of C++ slight-of-hand here by using std::filesystem::path to pull out the name of the application, or at least the name it was invoked by. This is an old trick in bash scripts and Python, but I’m using functionality from the C++ 17 standard.

How to Build

Building this is a one-liner:

g++ -std=c++20 SimpleFactorial.cpp -o SimpleFactorial

You’ll note that I didn’t use the -static switch. Since there were no specific Boost libraries to link, I decided to just build a regular Linux application that will dynamically link against standard Linux C and C++ libraries. Those are common to every modern Linux distribution.

Sample Runs

The first example shows what happens when you invoke SimpleFactorial without any arguments.

~ ./SimpleFactorial SimpleFactorial can be invoked with one or more integer arguments.Example: SimpleFactorial 10 20 30 40 50 60 70 80 90 100

So what happens if we invoke that example?

~ ./SimpleFactorial 10 20 30 40 50 60 70 80 90 100  10! = 3628800  20! = 2432902008176640000  30! = 265252859812191058636308480000000  40! = 815915283247897734345611269596115894272000000000  50! = 30414093201713378043612608166064768844377641568960512000000000000  60! = 8320987112741390144276341183223364380754172606361245952449277696409600000000000000  70! = 11978571669969891796072783721689098736458938142546425857555362864628009582789845319680000000000000000  80! = 71569457046263802294811533723186532165584657342365752577109445058227039255480148842668944867280814080000000000000000000  90! = 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000 100! = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

What happens if we invoke SimpleFactorial with bad input data?

~ ./SimpleFactorial 10 20 foo  10! = 3628800  20! = 2432902008176640000SimpleFactorial invalid argument: foo

There’s another “hidden” feature of the application. What happens if you rename SimpleFactorial to something else, invoking it differently than it’s original name? What if you rename your source and build the application as something else? That’s where the path function comes into play. Let’s see what happens if we change the name of the application by way of a soft link.

~ ln -s SimpleFactorial foo~ ./foofoo can be invoked with one or more integer arguments.Example: foo 10 20 30 40 50 60 70 80 90 100

What happens when we have an error with this new name?

~ ./foo barfoo invalid argument: bar

This capability doesn’t seem all that important, but for more complex applications it comes in quite handy when you’re chasing down errors from within a long log file.

If you’re wondering why I didn’t use Boost’s Program Options, it’s because I didn’t understand the documentation well enough to learn how to pass an arbitrary number of arguments to the application. Maybe later…

Link: https://www.boost.org/doc/libs/1_80_0/libs/multiprecision/doc/html/index.html

tinkering with c++ — boost::program_options — final update

This is my final listing for the small example program in the prior post. I present it again and comment on the changes made and why they were made.

#include <exception>#include <iostream>#include <boost/program_options.hpp>using std::cout;using std::cerr;using std::exception;using boost::program_options::options_description;using boost::program_options::value;using boost::program_options::variables_map;using boost::program_options::store;using boost::program_options::parse_command_line;using boost::program_options::notify;int main(int argc, char *argv[]) {options_description options("Options");options.add_options()("help", "Help using application.")("compression", value<int>(), "Set compression level to integer value");try {variables_map varmap;store(parse_command_line(argc, argv, options), varmap);notify(varmap);if (varmap.empty() or varmap.count("help")) {cout << options << "\n";}if (varmap.count("compression")) {cout << "Compression level set to "<< varmap["compression"].as<int>;() << "\n";}}catch (exception &amp;ex) {cerr << "Error: " << ex.what() << "\n";cerr << "\n" << options << "\n";return -1;}return 0;}

The using Conundrum

When C++ namespaces were introduced, they also provided a way to avoid having to preface every namespace element with the namespace name, such as std::cout. That feature was the using namespace declaration, such as using namespace std. My problem with that is it completely obfuscates those function that are part of the std namespace, which can be a real pain when you’re trying to read code you’re not familiar with, or worse, your code that you haven’t touched in a while under stressful circumstances. Of course if you don’t use using namespace std then you’re going to put the namespace declaration in front of every standard library element, wherever they’re being used. I was pleasantly surprised with the using variant, where the namespace can be combined with the element, allowing you to just use the element in the body of you code. That’s what I did in lines 5-13 highlighted above.

Two good things have occurred. First, I have a list of every element I’m using in all the namespaced libraries at the top of the code in a list I can quickly read. Second, I can use the bare elements within the code body, producing cleaner code, more quickly. It might not be perfect, but it’s better that the first two original options, and it’s a coding standard I think I’m going to stick with from now on.

The End of Line Conundrum

I have seen quite a bit of C++ code these days that uses "\n" at the end of a line, instead of std::endl. For example:

std::cout << options << "\n";

versus

std::cout << options << std::endl;

I haven’t read anywhere that might explain why it appears std::endl has fallen out of favor as it were. I personally would rather use the simpler form, and from this point forward, I will just as I have in the listing at the top of this post.

Wrap Up

I removed that last else statement because it made no sense to have it. I then broke up the remain if statements into two smaller conditional blocks, which makes it easier to read. The entire program is organized in a logical fashion from declarations to initializations to work sections, and the try/catch block handles all the exceptions you can throw at it. It might not be perfect, but it’s good enough for me. Bitter experience has taught me over the decades that perfect is always the enemy good.