C++ notes

This page contains notes and misconceptions I’ve had or heard about C++. I use it as a reminder for myself, but it may contain things that may interest you. Don’t hesitate to contact me if you think something is incorrect or missing.

Technical Rambling

Boolean integral values

Converting an integer value to a boolean results in false if the value is zero, true otherwise. Because of this, we tend to think that a conversion of the boolean true to an integer may result in any non-zero value. However, the standard guarantees that non-zero value to be 1:

[conv.integral] If the source type is bool, the value false is converted to zero and the value true is converted to one.

Thus, the following is correct:

bool const this_is_true = true;
int what_is_true = this_is_true;

assert(what_is_true == 1);

Pre-increment operator versus post-increment operator

Here is how are declared the pre-increment and post-increment operators for a class X:

struct X
{
	X & operator++();	// prefix ++a
	X operator++(int);	// postfix a++
};

The post-increment operator returns the original value of the object before it was incremented, it has to:

Thus, the idiomatic implementation of the post-increment operator of X is:

X X::operator++(int a)
{
	X previous = *this;

	// increment X by a

	return previous;
}

Because of this extra copy, using the pre-increment operator is the preferred way to increment a type in C++, unless there is a need to keep the previous value.

Smart pointers

Prefer using make_shared because shared pointers store extra heap-allocated counters, those are allocated at the same time of the shared object with make_shared so:

Also use make_unique (to be simmetric with make_shared) and also to address this issue:

void sink(unique_ptr<widget>, unique_ptr<gadget>);

sink(unique_ptr<widget>{new widget{}}, unique_ptr<gadget>{new gadget{}});

Conditional operator (ternaries)

The following is a valid C++ code:

int a = 10;
int b = 20;

((condition) ? (a) : (b)) = 42;

It is the equivalent of:

int a = 10;
int b = 20;

if (condition)
{
    a = 42;
}
else
{
    b = 42;
}

This is possible since the conditional operator can return lvalues in C++ (hopefully not in C):

[expr.cond] If the second and third operands are lvalues and have the same type, the result is of that type and is an lvalue.

Now, consider the following code that looks slightly different:

int a = 10;
int b = 20;

(condition) ? (a) : (b) = 42;

It happens to do something totally different:

int a = 10;
int b = 20;

if (condition)
{
    a;
}
else
{
    b = 42;
}

That’s because of the priority of operators: the ternary operator has a lower priority that the assignment one.

Primitive types and initializations

Consider the following code:

void f()
{
	int a;
	int b = 0;
	int c = int();
	int d();
}

Despite the apparences, I found this rather tricky.

int c = int(); is a bit misleading because common sense tells us it is the same as int c; but that is not the case. Here is what the standard says about it:

[8.5] Initializers

An object whose initializer is an empty set of parentheses, i.e., (), shall be default-initialized.

And:

[8.5] Initializer

To default-initialize an object of type T means:

  • if T is a non-POD class type (clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
  • if T is an array type, each element is default-initialized; otherwise, the storage for the object is zero-initialized.

This has some implications, for instance when creating a new entry in a map, the default constructor of the object is called, if your map contains integers, you can assume they’ll be initialized to zero, thus the following is safe:

#include <map>
#include <string>

void f()
{
	std::map<std::string, unsigned int> stats;

	++stats["events"];
}

Pointers

Function pointers

On most architectures, a function pointer is simply a code pointer, it holds the address of the first instruction of the function and so, its size is the same as a classic pointer.

I haven’t had the chance to verify this, but on some architectures they are pointing to descriptors (IA-64 and PPC64), which contain the address of the function as well as architecture specific data. A call to a function then reads the descriptor, set some register and actually call the function.

Though in practice function pointers are pointing to locations in memory, the C standard doesn’t enforce the size of function pointers, so there’s no guarantee that casting a function pointer to a void pointer is safe. Interestingly, POSIX requires that conversion to be safe because of dlsym’s API:

Note that conversion from a void * pointer to a function pointer as in:

fptr = (int (*)(int))dlsym(handle, “myfunction”);

is not defined by the ISO C standard. This standard requires this conversion to work correctly on conforming implementations.

So in practice, it is fine to cast function pointers to void pointers on POSIX compliant systems, despite the warning you get from your compiler.

Method pointers

Not yet investigated why, but the following is disturbing:

#include <iostream>

struct A {};

typedef void	(*fptr)();	// pointer to a function
typedef void	(A::*mptr)();	// pointer to a method of A

int	main()
{
	std::cout << "fptr's size:\t" << sizeof(fptr) << "\n";
	std::cout << "mptr's size:\t" << sizeof(mptr) << "\n";

	return 0;
}

Which gives:

fptr's size:	8
mptr's size:	16

References

Initialization of const T &

I find this confusing, references on const objects differ from references on objects, the following code is valid:

int const & i = 42;
std::string const & s = "hejsan";

That’s because the initializer of a reference on a const doesn’t need to be an lvalue. When it’s not, a temporary object with the same lifetime of the reference is created, if needed, implicit conversions are done. Remove the const and it won’t compile.

This can be handy when you have to call functions or methods that takes references on const objects and you don’t want to waste a few lines defining them:

void f(std::string const & s, std::string const & d)
{
}

int main()
{
	f("hejsan", "world");

	return 0;
}

Idioms

Meaningful conditions

Consider the following code:

if ((event_timestamp + lag) < now_timestamp)
{
	// do something
}

It takes some time to guess the purpose of the condition; in the middle of reviews of larger pieces of code, it is easy to loose track of what’s being done, especially when conditions aren’t as trivial as this one. A way to add some semantic is to use a temporary boolean describing the intent behind the condition:

bool const event_is_late = (event_timestamp + lag) < now_timestamp;

if (event_is_late)
{
	// do something
}

A more complex example:

if (event_timestamp != 0 &&
   (((event_timestamp + lag) < now_timestamp) &&
   get_option("store_late_events"))
{
	// do something
}

Becomes:

bool const event_is_late = (event_timestamp + lag) < now_timestamp;
bool const event_is_null = event_timestamp == 0;
bool const store_late_events = get_option("store_late_events");

if (!event_is_null && (event_is_late && store_late_events))
{
	// do something
}

Yoda conditions

Yoda conditions can help to avoid some mistakes when writing condition involving immutable things, the idea is to reverse the condition so the immutable is on the left side. For instance the following code compiles fine, despite containing a possible error:

int main()
{
	int i = 42;

	if (i = 10)
	{
		// do something
	}

	return 0;
}

The intent was to write i == 10, however it does something totally different: 10 is assigned to i, and because i is different from 0, we enter the condition. Enters Yoda:

int main()
{
	int i = 42;

	if (10 = i)
	{
		// do something
	}

	return 0;
}

That does not compile because you can’t assign a value to 10.

One could argue that Yoda is less useful nowadays because modern compilers issue warnings about such cases, suggesting to wrap the assignment with parentheses.

Tricks

Disclaimer: The cost of software maintenance increases with the square of the programmer’s creativity. (Robert D. Bliss).

Know if a class has a method

#include <string>
#include <vector>
#include <iostream>

#define DECLARE_CLASS_HAS_METHOD(NAME, METHOD)                          \
    template <typename CLASS>                                           \
    class NAME                                                          \
    {                                                                   \
        typedef char Yes;                                               \
        typedef long No;                                                \
                                                                        \
        template <typename T> static Yes check(typeof(&T::METHOD));     \
        template <typename T> static No check(...);                     \
    public:                                                             \
        enum { Value = (sizeof(check<CLASS>(0)) == sizeof(Yes)) };      \
    }

DECLARE_CLASS_HAS_METHOD(HasLength, length);

using namespace std;

int main()
{
  cout << "string has length() = " << boolalpha << HasLength<string>::Value << endl;
  cout << "vector<int> has length() = " << boolalpha << HasLength<vector<int> >::Value << endl;
  
  return 0;
}