Stop using bool in C++ for function parameters !

Introduction

This article deals with the use of bool in C++. Should we use it or not? That is the question we will try to answer here. However, this is more of an open discussion than a coding rule.

First of all, what is the bool type? A boolean variable is a variable that can be set to false or true.

Imagine you have a simple function to decide whether or not to buy an house, you may design it like this

Problems arrived!

Then, when you want to use it, you can do it like this:

There is no problem here, however the reader may not understand at first glance if the false means no pools, or if it means no energy saving lights.

So, you will try to change the function call like this:

Now you are happy, the reader knows exactly what bool means. Are you sure? A very thorough reader may notice that there is a reversal of the parameters.

How to solve the problem?

There is different ways to solve this type of problem. The first is to use a strong_type. There are many libraries that offer this kind of thing, however, a simple enum class can do the trick.

Not only will the reader know which argument corresponds to which parameter, but also, in the case of a parameter inversion, the compiler will not let the error pass

Let’s rewrite the function declaration:

Conclusion

I would encourage people not to use the bool type for function parameters. What do you think? Do you use bool everywhere?

Thanks for reading !

Having fun with Detection idiom and macros in C++

Introduction

The detection idiom is now well known in C++. However, some implementations necessitate a lot of boilerplate, and I do believe that macros may help here. So, let’s having fun with the detection idiom and macros in C++!

The goal with detection idiom in C++

The detection idiom is used to know at compile time if an expression is valid, or not. For example, it can be used to know if a class has a function, an attribute, an operator…

Our objective will be to write something like

It is easy to read. The is_valid function (actually, it will be a macro) just returns a helper function with 2 arguments that return true_type if the expression is possible or returns false_type otherwise.

Basic detection idiom in C++

To implement the idiom detection in C++, you may use polymorphic lambdas. If you chose this solution, you will need 3 things.

  1. The case where the expression is not possible
  2. The case where the expression is possible
  3. A wrapper to make things easier.

The first case is really easy

It is a function template that takes a function as a first parameter and returns false.

The second case is a bit more complicated.

The idea is simple, we test to call F with the given arguments, and we return true. The void() is used because the result of F(xs...) may returns something that implements operator,.

The third case is easy, it is just a simple wrapper.

The usage is easy, but requires a lot of boilerplate …

Making thing easier with simple macros

A simple macro could simplify things.

It is now much easier to read, however, it requires you to make a is_valid macro for each number of arguments, and it is not a good thing at all…

Going further with macros

Basics

The first thing you must understand is that recursive macros are theoretically impossible. Macros work by expansion. Each time you will expand a macro, the underlying called macros will be _painted blue_ and will not be called again. For example, if you use #define X X, when you will write X, it will not recurse until your death, it will just replace X by … X.

However, you may use indirect recursion ! Let’s make a first try

Do you think it will work? The answer is no. You expand X(), so you call X_I(). The call to X_I expands to X which is painted blue inside this expansion…

What we must do is to defer the call to X and the call to X_I and force expansion several times :

Now, we are ready. We must define an interface for our macro. I propose the following :

With such an interface, you will not be able to pass a list of arguments directly, so you must wrap them. A usual way to wrap arguments with macro is a parenthesis, so you will write is_valid((a, b, c, d), expr).

To deal with such list, the easiest way is to call another macro

This expression will lead to MACRO(a, b, c, d)

The complicate task

What will be difficult here is to create a macro that takes some arguments in a list and transform each value into auto &&x,

The goal here is to transform MACRO(a, b, c, d) into [](auto &&a, auto &&b, auto &&c, auto &&d);

An interesting idea is to create a MAP macro that takes 2 arguments. The MACRO to apply and the arguments. The macro to apply here could be a MAKE_AUTO(x).

Going that way, it is easy to understand that MAKE_AUTO(a) expands to auto &&a.

So, let’s design MAP. As I explained before, it will take 2 arguments, so we can lay:

This macro will work as follows. Take the first argument and apply the macro to it. When the tail is not empty, perform recursion on the tail. Here is my definition of MAP macro.

So, here are the explanations. The beginning is easy, you just call the MACRO with the first argument. Here is the definition of HEAD and TAILS

HEAD takes a list and returns the first element, TAIL returns the list of all elements but the first.

The second line is where the black magic occurs. Here are some high-level explanations before showing the code.

  • WHEN(c)(expr) will expand to expr only if c == 1
  • NOT(x) will expands to 1 if x == 0 and to 0 if x == 1
  • IS_EMPTY(args) will expand to 1 if the list of args is empty or 0 otherwise
  • DEFER_TWICE is needed because WHEN already make one expansion.
  • MAP_I adds a , and call MAP again

When you develop using macros, I advice you to use these 2 helpers:

The CAT macro will concat x and y, and STRINGIFY will transform arguments into a string. That may help you for debugging for example. The IMPL versions does not make expansion of arguments, but the not _IMPL versions do.

The first Macro we are going to define is NOT. It is easy to define

We use the CAT_IMPL and not the CAT version because x will be expanded here, so we do not need another level of expansion. For people coming from a functional world, it may remain pattern matching.

The second macro we are going to define is DEFER_TWICE. The idea follows the DEFER macro:

Let’s define the WHEN macro.

It is easy, we do pattern matching to a function that does nothing when c is 0, and we expand the other arguments when c is 1.

The most difficult macro to define is IS_EMPTY. I told you about an end sentinel. A simple end sentinel to use with macro is a parenthesizes. So, we just add () at the end of arguments. For example : (a, b, c, ()) represents a list that contain [a, b, c].

IS_EMPTY(x) must return 1 only when x looks like (()). Here is my solution

Now that we have an almost working solution (I said almost because MAP will not work with an empty list, but it is not difficult to fix that), we can make the definition for our is_valid macro.

The ADD_END_SENTINEL macro is easy. It just appends () to the list.

And now, we can enjoy our new function !

Conclusion

I hope you liked this article and you learned something. If you have any questions, feel free to ask, I would be glad to answer.

If you want to try it, here is the full implementation.

Blog talking about 3D rendering, Qt and C++