It took some while, but this will be the last post on the little wonders of C++. In the last 9 episodes I showcased some potential features (upcoming / available in C++14), some modern features (introduced in C++11) and golden classics.
I hope that this series helps a little bit in regaining faith in the future of C++. Another aspect is that people need to be convinced that C++ has indeed changer for the better. It does not only feel like a new language, it also behaves like a new one in many important areas. Overall it is the best standard we've ever had, and there is no reason to doubt that the next one will be worse.
So what are the topics for this episode? We will finally have a look at TMP, a topic that has been brought up many times. If we talk about TMP we need also to cover type traits. Additionally the placeholder variants of the new and delete operator will be discussed as well as the delegating constructor. Finally we will also mention the ability to include URLs directly in functions. This, however, is not that serious and should be considered to be a fun fact, not a desired feature.
Template Meta Programming
Shortly after the template mechanism has been introduced to C++ it was found that the template implementation offers a turing complete functional programming language. The key difference to usual C++ code is no the functional aspect, but the execution during compile time. Template meta programming utilizes the template mechanism to generate classes, functions and more during compile-time.
Of course templates have pros and cons. On the one hand we can avoid duplication and gain performance, on the other hand we might end up with more complicated code and an increased binary. The latter might result in instruction cache misses, but in practice this is rarely (or never) an issue caused by using templates.
What is one of the simplest examples of generating code during compile time? In my opinion Fibonacci is a great showcase. It illustrates the possibility to perform calculations during compile time. The calculation part is of course taken by the optimization part of the compiler. The template part does only result in an expansion of the expression, not in any direct calculation. The reduction happens in the optimizer.
Let's see some code to illustrate the magic of TMP:
#include <iostream>
template<int N>
class fib {
public:
enum {
value = fib<N - 1>::value + fib<N - 2>::value
};
};
template<>
class fib<1> {
public:
enum {
value = 1
};
};
template<>
class fib<0> {
public:
enum {
value = 0
};
};
int main() {
std::cout << fib<6>::value << std::endl;
}
Alright, what is happening here? We create a template class fib
that is parameterized by an integer N
. The template class contains an enumeration, which has a single value that is computed by the constant value of two other classes.
Using the enum
type is just a nice little trick to generate a (static) constant member of the class. Nevertheless, we also provide two template specializations of fib
, where the value of the constant (called value
) are set explicitely. Until this point nothing has happened. We only declared some stuff. Finally in the main()
function we trigger the template expansion by using fib
with an argument (in this case the number 6).
At this stage the template mechanism creates not only a version for fib
with 6, but also two more with 5 and 4. This again, results in two more with the arguments 3 and 2 (4 is already there, and three is actually requested twice, with only one being created). Finally the versions with N = 1 and N = 0 will stop the recursion. Once everything is expanded the optimizer comes to rescue and reduces everything to a single number, which is 8 in this case. Since we only use the constant of the class, we should see only a single number in the assembler code:
movl $8, %esi
movl $_ZSt4cout, %edi
Therefore the whole Fibonacci number calculation has been done inside the compiler. There are no runtime costs at all. Functional languages are fun, and using a functional language inside C++ to actually write less and more efficient code (however, less efficient in compilation) can be crucial in many scenarios. I wish C# would come up with a way to deal with smarter code generation during compile-time. But maybe with Roslyn and analyzers (and more) we already have everything that is needed to create something as awesome as TMP.
Type traits
Now that we have TMP the logical question is: Can it be used for something beyond computing Fibonacci numbers? Maybe for something that is not related to numerics or trivial mathematics. The answer to this question must be yes, otherwise the whole section would be rather short.
We have already seen that TMP relies on recursion. Obviously variadic templates (presented in an earlier post) can only be used and understood if TMP is already understood. Recursion is key and it is not possible to use the usual loop-constructors such as a simple for
. Similarly for conditions such as if
. But isn't that bad? Obviously conditions and loops represent what can be understood as programming. But wait... In the TMP section we already used an if
! Obviously we had an implicit if
, that distinguished between N
, 1
and 0
. Please note that we should have used unsigned int
, since using
std::cout << fib<-6>::value << std::endl;
could result in bad things. Luckily compilers have a recursion counter that prevents (at least direct) recursions. Usually we'll see something similar to the following output (generated by GCC):
tmp.cpp:7:31: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) instantiating ‘class fib<-1806>’
value = fib<N - 1>::value + fib<N - 2>::value
^
tmp.cpp:7:31: recursively required from ‘class fib<-8>’
tmp.cpp:7:31: required from ‘class fib<-6>’
Therefore the templating mechanism always takes the most specialized one. Each option represents a possible function to run, with parameters that represent the degree of specialization.
Therefore we can distinguish between different variants by offering different specializations. We can combine this with the overloading mechanism of C++. The result is a set of classes known as type traits, which are completely useless except within templates. There these classes may act as switches and enable calling the right method for the right specialization. Andrei Alexandrescu calls traits the else-if-then of types. In my opinion this description fits.
In C++11 the STL also added the <type_traits> header, which emphasizes the significance of type traits. The header is basically the same as previously known from the Boost library. Most of the common conditions can be written using this particular STL header.
Writing a type trait is similar to the fib
class before. The most important part is specialization. Let's see a trivial example:
template<typename T>
struct is_void {
static const bool value = false;
};
template<>
struct is_void<void> {
static const bool value = true;
};
Therefore the structure will always have a constant value
to be set to false
, unless the given type is void
. Then we consider it an empty type.
Alright, where do function overloads fit into that picture? Of course we can use them to insert some code at a specific position. Therefore we still have some kind of condition, but another kind of expansion than before.
#include <iostream>
#include <string>
using namespace std;
inline string show(double) {
return "double";
}
inline string show(int) {
return "integer";
}
template<typename T>
void showTheType() {
cout << "hi, my type is " << show(T {}) << endl;
}
int main() {
showTheType<double>();
showTheType<int>();
}
An inspection of the generated assembly code shows that two labels (_Z4showd
and _Z4showi
) have been created for the two used variants. Both load their string directly, i.e. there is no cost in calling the function. The function has been inlined. Additionally no argument is created. The argument has not been used previously and has therefore being omitted by the compiler. This is an optimization that always works if no constructor function has been supplied. Otherwise it depends on the optimization level and evaluation of the provided constructor function.
There is much more to write about, but I think the provided material is enough to get to know the feature and why it is important.
Placeholder new / delete
The new
operator is a tricky beast by itself, but besides its standard form it can be extended to also take additional (custom) parameters. Let's review the standard form first:
void* operator new(std::size_t size) throw(std::bad_alloc) {
using namespace std;
if (size == 0)
size = 1;
while (true) {
if (success)
return *mem;
new_handler globalHandler = set_new_handler(nullptr);
set_new_handler(globalHandler);
if (globalHandler)
(*globalHandler)();
else
throw std::bad_alloc();
}
}
The code above is obviously just a pseudo-code variant, however, it showcases some of the required tricks when creating a custom variant. We need to handle 0-byte requests and we need to make sure that an handler is called for freeing up some memory. There are a lot of constraints and obviously we have to be careful that all of these are handled appropriately. If we provide a custom new
operator we should also provide a custom delete
operator. But that does not bring us closer to the original topic.
Instead we want to provide a variant of the new
operator that takes further arguments for the allocating process. One could now ask several questions including "Why no just use malloc
and provide another method?" or "What's the purpose?". Well, if we consider the case of a special allocation, which should use a custom logger, then we certainly have to use another allocation technique. Also we might want to do something fancy and, e.g., create shadows on the file system of the fresh object. All these scenarios require further arguments in the allocation process.
The reason for not using another function is simple: We would still be able to call new
in our code. This is not wanted. We want to restrict object creation to the operator we've defined.
Alright, so how does the process look like? In fact it is not very different from before. In the next example we will just provide one further argument, which happens to be of type ostream
.
class Base {
public:
static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc) {
//hides the normal global form
/* ... */
}
};
Most importantly we will hide the normal global form by introducing our own version. This does only have consequences for allocations of the Base
class and derived ones. All other types can still be allocated with the usual, global form of the new
operator.
Let's see the immediate consequences in code form:
auto pb = new Base; //Oops! Standard form hidden
auto pb = new(std::cerr) Base; //Works
Maybe we only want this to happen for the Base
class and not all derived classes. Therefore we can redeclare the normal form. It is sufficient to declare it - there is requirement on re-implementing it. One example for this would be the following code:
class Derived : public Base {
public:
//redeclares the normal form
static void* operator new(std::size_t size) throw(std::bad_alloc);
}
We create a class called Derived
and redeclare the new
operator. Finally this hides the placeholder variant, as we have now our standard version restored. Now this results in more or less the opposite example code, compared to the previous one.
auto pd = new(std::cerr) Derived; //Oops! Now the placement is hidden
auto pd = new Derived; //Works
Two important remarks. First, as with replacing the standard new
operator we also should insert a placeholder delete
if we create a placeholder new
. Otherwise memory leaks may appear. Second, we should really make sure to know what we are doing. It is easy to unintentionally hide a normal version of, e.g., new
(there are three versions accessible at global scope). Hiding all these standard forms may be unwanted.
Delegating Constructor
There are many problems with constructors. This is especially true in C++. But every problem is there for a reason and we should not forget that constructors solve a lot of issus. In fact each problem with constructors is a solution to at least 3 other problems. Therefore each problem accounts for 2 benefits.
Of course there are exclusions of this rule. And a particular nasty one has been present for a long time: How to call another constructor in the same class? It just wasn't possible. Of course a work-around existed, which had other problems.
class Foo {
public:
Foo(int a) {
set_all(a);
}
Foo(int a, int b) {
set_all(a, b);
}
Foo(int a, int b, int c) {
set_all(a, b, c);
}
private:
int _a;
int _b;
int _c;
void set_all(int a = 0, int b = 0, int c = 0) {
_a = a;
_b = b;
_c = c;
}
};
Alright, how can we do it right? This is now a simple example with a simple workaround, but in reality code is more complicated and lengthy. Therefore any reduction and any statement of intention is welcome.
class Foo {
public:
Foo(int a) : Foo(a, 0, 0) {
}
Foo(int a, int b) : Foo(a, b, 0) {
}
Foo(int a, int b, int c) : _a(a), _b(b), _c(c) {
}
private:
int _a;
int _b;
int _c;
};
Why is this variation better? First we do not have the set_all()
method, which was only intended to be used by the constructor(s). Second we limit the unnecessary invocations. Before we did an initialization (even though we did never write it) and an assignment. Now its a direct assignment in the initialization. Finally we re-use existing code, but calling other constructors. Therefore we can just place some code in the last one, which will also be executed by the other two constructors.
Other languages such as C# had delegating constructors for a long time. It's a good thing to see C++ catching up here. Code reduction and emphasize on intention is important.
Urls in source code
What should be the last wonder in this last? There have been a lot of good candidates. I guess a good choice would have been the powerful ternary operator, which also allows writing the selected variable, if any. This enables, e.g.,
(a == 0 ? a : b) = 1;
However, in the end I picked a much simpler one, that turns out to be quite lovely. Sometimes we might end up decorating some code with an URL:
void foo() {
// http://www.example.com
/* ... */
}
Maybe the URL is too strongly hidden in this comment. Maybe we want to expose it a little bit stronger. Usually we - of course - won't, but let's think about it. It turns out that we can actually do it!
void foo() {
http://www.example.com
/* ... */
}
This is legitimate C++. Why? The magic starts with the colon, which triggers the first part to be a label. The label is called http
and explains why http-schema URLs can only occur once within a function. Now we might have a problem after the label. Nevertheless, since we start a line-comment directly after the label, we have no problem here at all. This means that the rest is just omitted by the parser.
Therefore URLs form a pretty valid syntax (within functions), by inserting a label (named after the URL's scheme) and commenting out the rest of the URL.
Nice coincidence, isn't it?