C++: The Good, The Bad and The Ugly

CPP in a nutshell

C++ is not exactly known for its elegant syntax or concise expressiveness. It is a pretty common joke to hear in the programming world that “C++ is the only language where the error message may actually be longer than the program that caused it”. Now, be that as it may, I think its more accurate to say that “C++ is the only language where the symbol name will be longer than the program that uses it”. Take the following example:

std::cout << game::mainloop::logger::statuslogger::outputstream::formatter::status.getStatusForOutput() << std::endl;

Fascinating stuff. You have symultaniously succeeded in creating the most pretentious and yet unreadable piece of code ever written. There is no person alive who can look at this and not think that this is ridiculous.

Now, if you thought that was bad, just you wait until you see some of the errors that same piece of code can throw at compile time:

src/scores-graphics.cpp:59:21: warning: loop variable ‘s’ creates a copy from type ‘const std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>
>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >’ [-Wrange-loop-construct]
   59 |     for (const auto s : sbddl) {

Yes, you read that correctly. The typename in that piece of code was

const std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>
>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >

Now, if you can please explain to me exactly what that typename is meant to store, please email me, because I want to give you a large sum of money. Another fascinating thing that C++ somehow succeeds in doing is making literally the most basic of problems really overcomplicated. For instance, what if I wish to copy a fundamental type (fixed size, less than processor word length - literally the most basic of CPU operations). Let’s take a look at a good example, but let’s initially write it in our good old friend C:

int var = 5;
int copied = var;

As you can see, we have created a value var with a static value of “5”. We then copied this value into a new variable called “copied”. This can be implemented with a single movl instruction under the hood: it’s really simple. But not simple enough for C++, of course! Instead, C++ has to have a concept of “copy constructors”, which means that this literal single instruction operation instead has to compile to a load of garbage. It also means that passing variables to functions is never safe again, or else you get errors like this:

gameboard.cpp:138:23: warning: loop variable ‘current_offset’ creates a copy from type ‘const point2D_t’
-Wrange-loop-construct]
  138 |       for (const auto current_offset : offset_check) {

Now, two problems are posed here. First off, why the hell is it a problem that I created a copy? We do this all the time! What’s the problem, C++? In fact, in this case, the actual problem is that the variable is not a fundamental type, so the copy constructor must be called in order to create a copy of this variable. So, in effect, there is no problem; the code is simply less efficient than if a pass by reference were to be used. I had to dig through the GCC manual to find the documentation for that specific warning ID in order to find out why this is even a problem. Whereas, in C, the most complex error or warning you will ever get will at the very least have have a definitive reason for this warning being there, rather than the compiler making arbitrary demands for the purpose of “performance and readability”. The second problem is the fact that (although this would make you completely unaware of it), this copy has also called a copy constructor. The error does not mention this at all, but this could leak memory. Just more of the issues with the implicit behavior which so often comes with C++.

Now, my pet hate in C++ is the ambiguous syntax. For instance, what does the following code do:

MyClass instance();

What in God’s name is that supposed to mean? Are we calling a function with the name “instance”? If so, then why is there a type name in front of it. If this is a variable, why are we calling it like a function. What on earth is this? Well, that this actually does is creates a new instance of the object “MyClass”, calling the default constructor. Great, totally clear. You see, the way we do this in C is:

MyClass instance;
create_myclass(&instance);

/* Or, alternatively */
MyClass *alternate = create_myclass_a();

In both cases, it is absolutely clear what we are doing. But, if you thought that was bad, just wait until you discover implicit casting. For instance, observe the following code:

MyClass instance = 2;

Surely this is a compiler error. Surely… Nope, that’s totally valid C++, assuming that a constructor exists which can implicitly convert this. What this will actually compile as is equivalent to the following:

MyClass instance = MyClass(2);

Why did this need to be a thing? Seriously, when does this produce better code than just writing good code? I genuinely don’t understand. What is the point in this feature? Well, after in-depth research, it’s apparently for situations like the following:

/* Will create a vector with 5 somewhere? */
Vec2 vector = 5;

Now, what does this code do? Good question because even this code, with its fancy implicit conversions and casting, is now ambiguous. What field in the vector does this variable get assigned to? What is the state of the vector after the conversion. In order to find out, you will have to dig through the source code for the constructor (assuming you find the same one which the compiler decided to use for the conversion, which is not guarunteed).

Finally, we have reached quite possibly the most annoying feature (in my opinion) of the entire C++ language: modifiers. For example, take the following snippet:

const int func(int a, int b);

Now, what does this actually do? Well, this function is returns a constant integer type. So far pretty simple. However, what about the following:

int func(int a, int b) const;

Same thing, right? No, of course not - this is C++ we are talking about. The movement of the const over to the other side of the function obviously means that it applies to the function and therefore means the following: this function is prevented from modifying global state or the state of any object which this function is a part of. Ok, so that’s kind of dumb, but not too bad yet, right? Well…

const int func(const int a, const int b) const;

This is still valid C++, by the way. Now, we are pasing two constant integers (why this needs to be the case is beyond me, saying as we are passing by value), returning a constant integer and are prevented from mutating global state. Now, this all doesn’t seem too bad, until you get something like this:

If you think C++ is not overly complicated, just what is a protected abstract virtual base pure virtual private destructor and when was the last time you needed one?

Tom Cargill

I thought this was just a joke. So I looked it up. No.

That is a very real name of a very real language construct in C++. Its definition contains the word const at least three times. I didn’t bother to write out the rest.


If you too join me in really not liking C++ (or, at least, modern C++), please see the following to assist in your initiation into the cult of C99:

Ethan Marshall

A programmer who, to preserve his sanity, took refuge in electrical engineering. What an idiot.


First Published 2021-07-25

Categories: [ Old Blog ]