Tuesday 18 August 2020

The pain(s) of C++11


Recently I wrote a small cross-platform library of widgets and didn't think much about the C++ version to be used, as my client's primary platform was Windows plus the latest Visual Studio with C++17. As the Microsoft compiler was normally the slowest to implement new standard's features I didn't worry about problems when compiling the code with GCC on Linux and just settled on the C++17 standard (or at least the subset of it supported by the used Visual Studio version).

However.... it all came out rather differently. 
 
First, I had to back-port it to Visual Studio 2015, which theoretically supported C++17, but didn't have std::optional in its STL 😨, but this is material for another article...

If that weren't enough, it turned out that the GCC version used in our build server doesn't support C++17, so I had backport it again to C++11. OK, I thought, C++11 was the giant step for C++ so it shouldn't be that difficult to go back, all the most important language features should already be present there. 

You'll imagine my surprise, when this innocent code snippet failed to compile on the build server!
   // impl. helpers:
   template<typename Enum>
      constexpr auto enumToInt(Enum e) noexcept  // C++20: requires(std::is_enum_v<Enum>) 
   {
      return static_cast<std::underlying_type_t<Enum>>(e);
   }
You see, optimistic as I am, I was already thinking about what would be possible in C++20!

I won't show you the exact error message because it is incomprehensible for most people*. Enough to say it wouldn't compile.

So this is what I had to use instead:
   template<typename Enum>
      constexpr auto enumToInt(Enum e) noexcept        // C++20: requires(std::is_enum_v<Enum>) 
        -> typename std::underlying_type<Enum>::type   // C++11 backward compatibility :-(
   {
      return static_cast<typename std::underlying_type<Enum>::type>(e);
   }           
As we can see, I needed first to sptinkle some typename keyword's instances all over the place to help and guide the compiler in parsing of the template. Secondly, in C++11 the XXX_type_t shortcuts for the standard library types aren't available. I had also to explicitely specify the return type of the lambda.

You see - much more mindless typing in the C++11 version! In some respect it's almast as verbose as the older Java releases. And not to mention lots of time used for deciphering template error messages to find out those problems!

Conclusion:

In conclusion I must say, we should appreciate C++14/17 much more than we do, because it really saves us the time to write and later to read the code!!! Somehow I wasn't fully aware of that.

P.S.:
Imagine, at first I was even tempted to use this monster:
   template<typename Enum>
      constexpr auto enumToInt(Enum e) noexcept 
        -> decltype(static_cast<typename std::underlying_type<Enum>::type>(e)) // C++11 backward compatibility :-O     
   {
      return static_cast<typename std::underlying_type<Enum>::type>(e);
   }   
--
* Personally. I always found the complaints to be somehow exagerated - with a little practice you can find the problem in not too long a time.  But it is a quest and not an automated procedure. On the other hand, isn't it always like that with compilers? Good error messages are rather a recent invention and still more like an exception than a rule.


A study in bad naming


Recently, while working for one of my clients on someone other's code I spotted the following definitions of members in some class:

What's that???!!!  Can you see it? Are you as much shocked as I was on its first sight? You are not? OK, I will explain my abomination - every declaration had to be commented! And that's not enough - the comments are essential in understanding the purpose of each member!

Why I do not like it, you might ask? Well, I'm lazy and commenting each declaration seems like too much work... You might ask 'why?' again - well, if I would like to add some definition to this code, I'd have to write a comment as well in order to maintain the (arguably flawed) coding style! I'm only a service provider for my client and wouldn't like to break this code's look and feel.

As you might know I'm not a friend of the newfangled "no comments" politics, but this is an example where we could definitevely use some of it. But why's that so bad again? It's because you could fix the example code with careful usage of naming* and it's even not that difficult! 

Let's have a go at it:
  /* State m_state => */ State m_dateValidity;
here we don't have to include "state" in the name, as it is already stated as the domain of the variable.
  /* QAction* m_action => */ QAction* m_datePickerPopupAction;
Here, however, we somehow inconsistently included "Action" in the member's name mainly because of my vague gut feeling. In this instance we could indeed try to improve the naming by considering shortening it, maybe even to m_datePickerAction;
  /* QDate m_date => */ QDate m_parsedDate;
A no-brainer.
  /* QStringList m_parseFormats => */ QStringList m_dateParsingFormats;
Another one!
  /* bool m_twoYearIsPast => */ bool m_treat2DigitYearsAsPast;
Here we could try to make the name a little better - maybe m_twoDigitYearsInThePast
  /* QDate m_minDate => */ QDate m_earliestDate;
As minimum and maximum are't commonly used with dates.
  /* QDate m_maxDate => */ QDate m_latestDate;
Idem.
  /* bool m_justFocused => */ bool m_focusReceived; // used for workaround ABC... 
Here I see the only place where a comment is needed.

So, what do you say? It wasn't even that difficult. Arguably the result isn't perfect but it's a lot better than the original. Maybe it's even good enough? 

So watch your names, they are important - because code readibility is important!

--
* As they say -"there are only 2 hard problem in comp. sci. - cache invalidation and naming things"...