The Fugue Counterpoint by Hans Fugal

6Dec/130

Reduce compilation time with template magic.

It is a common belief that templates must be implemented in header files, a lamentable situation that greatly increases build times because those full-blown headers must be compiled every time they are included. It also makes the headers less useful as brief documentation, which one of the things about headers that is actually good.

But that is not strictly true. You can separate template declarations and implementations. With a simple pattern and just a little thought, you can compile your template code once and link it just like you do regular objects or functions.

Let us begin with a function foo which just prints the T it was given to cout. Here is the header:

// foo.h
#pragma once

template <class T> void foo(T);

Now let's try to use it:

// a.cc
#include "foo.h"

int main(void) {
    foo(42);
    return 0;
}

And compile:

$ c++ -std=c++11 -c a.cc
$ c++ a.o
Undefined symbols for architecture x86_64:
  "void foo<int>(int)", referenced from:
      _main in a.o
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status

The object file compiles fine, but linking fails because void foo<int>(int) is undefined. So, let's define it:

// foo-tmpl.h
#pragma once

#include "foo.h"
#include <iostream>

template <class T> void foo(T t)
{
    std::cout << t << std::endl;
}

Now if we change a.cc to include foo-tmpl.h instead of foo.h, the implicit instantiation will work fine. But instead, let's explicitly instantiate the int specialization in foo.cc, because we believe everyone and their dog will want the int specialization, and we want to save compile time:

// foo.cc
#include "foo-tmpl.h"

template <int> foo(int);

And compile:

$ c++ -std=c++11 -c a.cc
$ c++ -std=c++11 -c foo.cc
$ c++ a.o foo.o
$ ./a.out
42

Success!

Ok, but what about implicitly instantiating templates? We don't want to give up this unique power of templates. We can do it. This is why I put the implementation of foo in foo-tmpl.h instead of foo.cc. When we need to instantiate, we include the -tmpl.h instead of the .h. We should try to avoid doing this in other headers, and prefer to do it (once) in an implementation file. To demonstrate let's introduce a new function bar:

// bar.h
#pragma once

void bar();

And its implementation:

// bar.cc
#include "bar.h"
#include "foo-tmpl.h"

void bar()
{
    foo("bar");
}

Now we are implicitly instantiating void foo<const char*>(const char*), but that's ok because we included foo-tmpl.h. Now we can use bar:

// b.cc
#include "bar.h"

int main(void) {
    bar();
    return 0;
}

Compile and run:

$ c++ -std=c++11 -c b.cc
$ c++ -std=c++11 -c bar.cc
$ c++ b.o bar.o
$ ./a.out
bar

And finally, let's use them together

// c.cc
#include "foo.h"
#include "bar.h"

int main(void) {
    foo(42);
    bar();
    return 0;
}

Compile and run:

$ c++ -std=c++11 -c c.cc
$ c++ c.o foo.o bar.o
$ ./a.out 
42
bar

Just to make sure we understand what's going on under the covers, let's look at the symbols with nm, and make sure things are defined where we expect them to be:

$ nm *.o | c++filt  # manually-filtered output follows
a.o:
                 U void foo<int>(int)

b.o:
                 U bar()

c.o:
                 U bar()
                 U void foo<int>(int)

foo.o:
0000000000000000 T void foo<int>(int)

bar.o:
0000000000000000 T bar()
0000000000000075 S void foo<char const*>(char const*)

I performed these explorations with gcc 4.8.


$ c++ --version
c++ (MacPorts gcc48 4.8-20130210_0) 4.8.0 20130210 (experimental)
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

With C++11 you also have extern template at your disposal, which lets you avoid implicit instantiation. So if you have many files which instantiate the MyClass specialization, and don't want foo.cc to even be aware of MyClass, you can add this to MyClass.h:

// MyClass.h
#include "foo.h"
class MyClass {
    //...
};

extern template void foo<MyClass>(MyClass);

And then MyClass.cc looks something like this:

// MyClass.cc
#include "MyClass.h"
#include "foo-tmpl.h"

// explicitly instantiate templates
template void foo<MyClass>(MyClass);

MyClass::MyClass() {
    // ...
}

// ...

Now the MyClass instantiation of foo will only be compiled once in MyClass.cc, but can be used in umpteen other files.

26Mar/0818

QCad on Leopard

I finally got around to building QCad on OS X Leopard. There are two main hurdles: getting Qt3 to build and getting QCad to build.

At first I tried building Qt3 with macports, but building QCad was a royal pain with the X11 version of Qt3 on OS X, for whatever reason. So I tried to install the qt3-mac MacPorts package, but that failed. So I was on my own building Qt3.

This patch will allow Qt3 to build on Leopard, by following the instructions in the INSTALL file. Here's the diffstat:

 config.tests/mac/mac_version.test |    2 +-
 src/kernel/qcursor_mac.cpp        |    4 ++++
 src/kernel/qt_mac.h               |    2 +-
 src/tools/qglobal.h               |    5 ++++-
 4 files changed, 10 insertions(+), 3 deletions(-)

I put it in /Developer/qt3, and I wrote a script to source on demand rather
than setting QTDIR and friends in my .profile or .bashrc, since I more
often want Qt4 than Qt3. I configure with -static, so applications like QCad
are built with Qt3 statically, which just makes things work better.

QCad needs a patch as well:

Index: qcad-2.0.5.0-1-community.src/mkspecs/defs.pro
===================================================================
--- qcad-2.0.5.0-1-community.src.orig/mkspecs/defs.pro  2008-03-26 08:46:25.000000000 -0600
+++ qcad-2.0.5.0-1-community.src/mkspecs/defs.pro       2008-03-26 08:46:48.000000000 -0600
@@ -1,6 +1,6 @@
 # $Id: defs.pro 606 2004-12-25 03:08:40Z andrew $
-QMAKE_CXXFLAGS_DEBUG += -pedantic
-QMAKE_CXXFLAGS += -pedantic
+#QMAKE_CXXFLAGS_DEBUG += -pedantic
+#QMAKE_CXXFLAGS += -pedantic

 win32 {
   QMAKE_CFLAGS_THREAD -= -mthreads
Index: qcad-2.0.5.0-1-community.src/scripts/build_qcad.sh
===================================================================
--- qcad-2.0.5.0-1-community.src.orig/scripts/build_qcad.sh     2008-03-26 08:46:06.000000000 -0600
+++ qcad-2.0.5.0-1-community.src/scripts/build_qcad.sh  2008-03-26 08:46:49.000000000 -0600
@@ -30,7 +30,7 @@ then
     export MAKE=gmake
     echo "Platform is Solaris"
     platform=solaris
-elif [ "x$OSTYPE" == "xdarwin8.0" ]
+elif [ "x$OSTYPE" == "xdarwin8.0" -o "x$OSTYPE" == "xdarwin9.0" ]
 then
     export MAKE=make
     echo "Platform is Mac OS X"

Then do

cd scripts
./build_qcad.sh notrans

It will complain about not finding qm/*.qm, but that's a nonfatal error.
QCad.app will be in the qcad directory, ready for your use.

I built this on an Intel MacBook running Leopard. If you think that matches
your setup, you're free to download my QCad.app and avoid
building both Qt3 and QCad.

Tagged as: , , , , 18 Comments
20Mar/080

What was that macro?

I find myself asking this question a lot: "Now what was that define that detects annoying platform that makes porting otherwise perfectly-portable code difficult?"

This will narrow your search down to something useful:

echo | cpp -dM

Go ahead, try it. I wish I'd known this one earlier.