Hello and welcome to C++ Weekly. I'm your host, Jason Turner. In this episode I'm
going to pick up where I last left off when we were discussing link time optimization
and share a very similar example related to templates.
Okay, so when we were talking about link time optimization I left off with this
example of this thing that can add values, and we've got a main function that's calling an "add"
function. It's calling this value_2 and all this stuff. In the previous example I had the "add"
function as a regular C++ function that was forward declared and in a separate compilation
unit. For the sake of this episode I have moved the "add" function into being a template function.
Now it knows how to add values of any two types. I'm currently doing an unoptimized build to make
a point - file1 is calling the "add" function and file2 is also calling the "add" function.
I have the output of file1 over here on the right hand side and we can see that it has
an implementation of the "add" function, and if I change to look at the output of file2
then we can see that it also has an implementation of this "add" function. Now the "add" function
is very small, so it doesn't take much effort for the compiler to compile it,
but the fact remains that the compiler did have to compile this "add" function
twice because it's a template. That's how templates work. The function is going to be
automatically created dependent on the types that were provided to it, and the compiler
is going to have to figure out what that means in the context of every file that you compile.
So C++11 added this feature called 'extern template' and the whole goal was to make it so
that you didn't have to recompile these template functions every single time you went to use them
by telling the compiler, "I promise that somewhere there will be a version of this template
that has already been created for you." So first of all I'm going to put this in -O3 just to show
you that this "add" function does still go away in the case of an optimized build and that's going
to be important information to keep in mind. So here where I have declared this template function.
I'm telling the compiler that "somewhere there will be provided for you a version of this
template, you don't have to worry about compiling it right now because a version
will exist." So I have declared the 'extern template', but you can see I am getting this build
error - "undefined reference to 'int add (int, int)'." So this is how I declared it - I'm
saying it is a version of the "add" template function with an int parameter that takes an
int (two ints) and returns one int. So I need to now actually have the compiler provide this
instantiation of this template function for me,
and it is just that simple. I have in this C++ file asked the compiler
explicitly to create this template function for me and I no longer get the linker error
and I've saved the problem of each file having to create its own version of this template,
but I've created a new problem because, well, is it a problem? It depends on your perspective. If
this "add" function were very large, requiring instantiation of many other template functions,
then I could have just made a considerable compile time performance improvement,
and I have actually used this in real code to make a significant impact on build times,
but now I don't get that optimization that I would really like to see, and
if I go back here to the full optimized build and I'm looking at the test binary I can see that I do
actually have to call this "add" function many times. The compiler simply isn't able to do that
kind of optimization that we wanted it to do. We are back in the world as if this function wasn't
declared in a header file at all, as if we had the file implemented in a different C++ plus file,
and now we're losing this optimization that we wanted; but we can solve that problem by again
enabling inter-procedural optimization, which is why I recorded the previous episode. So if
I enable inter-procedural optimization then I get this "argc + 5" - exactly what I wanted. In a way
we get to have our template cake and eat it too, would be one way to put this. So we get presumably
very fast individual cpp file build times - much faster than if we had to create the template each
and every time, and then we get to decide - do we want to enable inter-procedural optimization
to get back some of the potential performance that we lost but then pay a higher price at link time,
or do we just say, "It's okay to out of line this function because it's a large expensive function
anyhow, and the probability that the compiler was going to inline it, even in the case of
IPO being turned, on was low?" So these give you all of the choices that you could possibly want.
So there is another tool in your C++ toolbox to decide exactly where you want to spend time
and whether or not this cross-compilation unit, inter-procedural optimization is important to you,
or whether or not you would rather have faster link times. Just for the record,
in some cases IPO can actually result in a faster overall build times and higher optimization,
but I wouldn't count on it. These are all things that you have to test in your own code base,
but be aware of 'extern template' and provide these for your users if you have a very common
and very expensive template function that's used throughout your code base. If it's very expensive
it's not being inlined anyhow, so you may as well just go ahead and mark it as 'extern template'. So
thanks for watching this episode of C++ Weekly. I hope that this new tool that you may not have
known about before is useful to you and that you can find a place to use it in your own code.