C++ fun strange facts

The title might be a little bit misleading because, on one hand, you might not find these things funny if you are stumbling upon them and not understanding what is going on, and, on the other hand, they are not really strange when you pay attention to what is going on. However, here is a list of five (randomly picked) C++ features that would probably get you giving a second thought to what’s going on.

Aggregate initialization

Consider the following structure:

You can write the following:

But should you delete the default constructor as follows:

Then you can still intialize objects of this type but only using brace initialization (foo{}):

foo f; is no longer legal because foo does not have a default constructor anymore. However, foo f{}; is still legal because classes with deleted default constructors can be list initialized via aggregate initialization but not value initialization. For more info see 1578. Value-initialization of aggregates.

Alternative function syntax gotchas

Alternative function syntax refers to putting the type of the return value of a function at the end after the function type, as in auto foo() noexcept -> int. However, it doesn’t always go as smooth as that. Let’s consider the the following base class:

How do you write an overriden foo() in a derived class using the trailing return type (aka alternative function syntax)? If you’re tempted to do it like this then you’re wrong.

This will generate a compiler error (which differes depending on your compiler). The reason is that override is not part of the function type, so it has to be written after the function type. In other words, the correct syntax is as follows:

For more pros and cons on using the alternative function syntax see this article.

rvalue references and type deduction

I have updated this section to refer to universal references as forwarding references, as this is now the official term for these references, as noted by Marco Arena. For more, see Universal vs Forwarding References in C++

rvalue references are specified with && but in type declaration && could mean either rvalue reference or forwarding reference. The latter one is not a term that was preferred to universal reference, which was a term coined by Scott Meyers. It refers to a reference that can be either lvalue or rvalue. However, when you’re using && as parameter in function (templates) the meaning of && depends on whether type deduction is involved or not; if type deduction is involved, then it is an forwarding reference; otherwise, it is an rvalue reference. Here are some examples:

When you see something like T&& that means forwarding reference; however, if anything else is involved, like a const qualifier, such as in const T&&, then you have an rvalue reference. Also, if you have a std::vector<T>&& then you’re dealing with an rvalue reference. In this case, foo exists within the context of std::vector<T>, which means T is already known and does not have to be deduced.

There is actually a long article about this topic by Scott Meyers called Universal References in C++11. You should read it for a detailed look at the differences and caveats of rvalue and forwarding (aka universal) references.

std::array is not an array

Consider the following snippet:

What do you expect this to print? Well, the answer is 1 and 0. If you’re surprised, then remember std::array is not an array, but a standard fixed-length container that has the same semantics as a struct holding a C-style array T[N] as its only non-static data member. And, unlike a C-like array, it doesn’t decay to T* automatically. On the other hand, std::is_array is conceptually defined as follows:

And that is why std::is_array<std::array<T, N>> is std::false_type.

Indexing arrays

I have to admit I only saw this a couple of times in my whole life, and although I don’t remember exactly where, it was problably some obfuscate code. The following is valid code:

This changes the second element of arr (at index 1) from 2 to 42. 1[arr] is equivalent to arr[1], which in turn is an alternative syntax for *(arr + 1). Therefore, generally speaking, a[n] and n[a] are equivalent because the compiler would transform that expression in either *(a + n) or *(n + a), which are equivalent. Therefore, all of these are valid and equivalent:

I think it could be possible for the compiler to differentiate and make constructs such as 1[arr] illegal, but then again nobody actually indexes arrays like that, so I suppose it was never an issue.

2 Replies to “C++ fun strange facts”

  1. Just a small observation: The term “Universal reference” – originally coined by Scott Meyers – has been replaced with “Forwarding reference”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.