Programmer. (Many Interests: Philosophy & Mathematics)

Home

Constexpr Magic - Cpp 11

Constexpr – A magical keyword in the wizarding world of C++ that allows both wizards and muggles(me) alike to indicate to the compiler that the values of the variables or the functions marked constexpr could actually be evaluated at compile time and save us a significant amount of run-time overhead involved in evaluation of the expressions. The thing to note here is the use of the phrase “could be”, because marking a variable or a function constexpr does not necessarily guarantee compile time evaluation.

And as the saying by uncle ben goes, _With great power comes great responsibility_, one cannot just simply mark anything and everything constexpr , there are a set of requirements that the variables and the functions must satisfy to be called fit for them to be constexperized.

A variable must be:

  • of a literal type
  • initialized immediately
  • the expression involved in initialization must be a constant expression, whether it calls a function, a constructor or anything.

A function must:

  • return a literal type
  • only take literal types as its parameters
  • the expression involved in initialization must be a constant expression, whether it calls a function, a constructor or anything.

Note: Don’t worry if you don’t understand the constraints mentioned above. You will still be able to understand the article nonetheless. (although you can still refer to Literal Types to understand better).

Let us look at it with an example.

After having worked for 3 years as a Software Engineer and as a C++ developer, the only place I got to use one was when me and a colleague (phani) of mine were actually maintaining a legacy code base with macros all over the place. This is not to say that constexpr can’t be used elsewhere, it is just that we didn’t encounter a situation that needed it. So there was a requirement that came up wherein they wanted us to change the parameter to a macro to an integer that takes number of days since epoch as input and uses it inside a class template. Now the problem is that parameter i.e., number of days since epoch must be calculated at compile time due to the usage of the parameter in a static_assert, and guess what? we are scratching our heads thinking how to do it. And to no ones surprise it is this magical tool that came to our rescue. Lets look at it with an example(a trimmed down version of the problem we had):

MACRO that takes number of days from epoch as input:

#define IS_UNIQUE_ID(T, N)                                     \
template<>                                                     \
struct IsUniqueId<T>                                           \
{                                                              \ 
  static_assert(T::id == N, "please use a different date");    \
};        

Let’s say we have a class that has the following unique id in some file. Here, unique id is nothing but number of days since 01-01-0001 i.e., you pass the date in the below mentioned format – yyyy, mm, dd and the function will calculate the number of days from 01-01-0001 to that date. And then the object id will be assigned the calculated number of days.

class A {
public:
    static const unsigned id = getNumberOfDaysFromEpoch(2019, 4, 27); /* for example */
/* other members and member functions */
}; 

Now what we want is something like this, meaning, a macro that can compare the static id and the return value of the function passed as second parameter to the macro.

IS_UNIQUE_ID(A, getNumberOfDaysFromEpoch(2019, 4, 27)) /* you could have any date*/

In order to do that, we must first ensure that our calculations are done at compile time. But how do we do that? Simple – add constexpr; but to add that we must ensure that the logic required to calculate the number of days is done in a single line. Crazy isn’t it, that is c++ for you.

Let us not think much and start with writing our logic. I have a feeling that we will figure out something as we go.

Note: If you are here only to see the use-case of constexpr, you can skip this part and jump to the code listing at the end of the post.

Warning: The following part explains the logic behind the code, this may be boring or irritating to many.

Firstly, we need calculate the number of days between 1-1-0001 and user provided date(one provided inside getNumberOfDaysFromEpoch function), and then repeat the same for 1-1-0001 and 1st Jan, 1970. Once we have the number of days, all we have to do is then subtract the number of days found by using the user provided date from the number of days found by using the epoch date. But before that we need to take care of the corner cases involved in a leap year as simply dividing by 4 won’t yield us the correct number of leap years for us to be able to calculate the number of days accurately. Let us look into that.

In the Gregorian calendar, a normal year consists of 365 days. Because the actual length of a sidereal year (the time required for the Earth to revolve once about the Sun) is actually 365.25635 days, a “leap year” of 366 days is used once every four years to eliminate the error caused by three normal (but short) years. Any year that is evenly divisible by 4 is a leap year: for example, 1988, 1992, and 1996 are leap years.

However, there is still a small error that must be accounted for. To eliminate this error, the Gregorian calendar stipulates that a year that is evenly divisible by 100 (for example, 1900) is a leap year only if it is also evenly divisible by 400.

For this reason, the following years are not leap years:

1700, 1800, 1900, 2100, 2200, 2300, 2500, 2600

This is because they are evenly divisible by 100 but not by 400.

The following years are leap years:

1600, 2000, 2400

This is because they are evenly divisible by both 100 and 400.

/* helper function to get the number of leap years since 1-1-0001*/
constexpr unsigned getNumberOfLeapYears(int year)
{
    return (year/4 - year/100 + year/400);   
}

For the same reason as stated above:

/* helper function to get if a given year is a leap year */
constexpr bool isLeap(int year)
{
    return (year%4 ==0) ? ((year%100) == 0 ? ((year%400) == 0 ? true : false) : true) : false;   
}

Below is the logic to calculate the number of days from 1-1-0001 i.e., if the given year is a leap year and the month is greater than February the extra day gets introduced as part of the leap year which would be 29th February, hence we add 1 to the total number of days since 1-1-0001.

/* to get number of days from 1st of jan */ 
constexpr unsigned cummulativeDays[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 335}; 

/* driver function to get the number of days from days since 1-1-0001 */
constexpr unsigned getNumberOfDays(unsigned year, unsigned month, unsigned day)
{
    return (isLeap(year) && (month > 2)) ? ((1 + getNumberOfLeapYears(year - 1)) + (year - 1) * 365 + cummulativeDays[month - 1] + day) : (getNumberOfLeapYears(year - 1) + (year - 1) * 365 + cummulativeDays[month - 1] + day);
}

And for the finishing touch let us create a helper function to subtract the given date from 1st jan 1970 to get the number of days.

/* yes, this just substracts the two dates */
constexpr unsigned getNumberOfDaysFromEpoch(unsigned year, unsigned month, unsigned day)
{
    return (getNumberOfDays(year, month, day) - getNumberOfDays(1970, 1, 1));
}

The reason the post is titled “Constexpr Magic C++11” is because we were constrained by a compiler at work that supported only C++11 and apparently C++11 standard does not allow for multiple return statements or declaration of variables inside a constexpr function. Although this is not the case with C++14/17/20, where the limitations of C++11 have been addressed, it still was a cool experience to innovate in a constrained environment.

Happy Coding ! Full Code Listing Below.

Working example can be found here: https://coliru.stacked-crooked.com/a/813605bc36562ead