Type traits

... by popular demand. Here we go! We take a look at how to obtain a type-traits implementation for C++, what they are good for and go through some easy examples to understand what they do.

Where to get them

Before we start, you'll need a type-traits implementation. Boost has a very nice one, which is fully modularized. You can include it by using #include which will put it into the std::tr1 namespace that I will use in the examples. Optionally, the TR1 additions to C++ contain it (for example, the Visual C++ Feature Pack has TR1 support).

What are they

Well, type-traits allow you to make compile-time decisions based on the properties of a type. Sounds kinda complicated? Well, think about it like this: You have a template class, and the user passes in a type you never heard about. With type-traits, you can get information about the type. The result of a type-trait is passed by its member value which is either true or false. Moreover, there is a type which is a real type (integral_constant, but I'll omit it here and write a small bool-wrapper-type). We'll be using this helper structure which turns a boolean into a type:

struct True {};
struct False {};
template <bool b> struct BoolToType;
template <> struct BoolToType<true>
{
    typedef True type;
};

template <> struct BoolToType<false>
{
    typedef False type;
};

Obviously, BoolToType<true>::type is True. This comes in handy later.

How to make use of them

Let's assume we are implementing a copy routine: fast_copy (const T* source, T* destination, size_type count). Now, if T has a trivial assign implementation (trivial meaning an assign implementation which is just a memcpy) we can just use memcpy, otherwise we have to go into a loop and copy using T::operator=. Now, how can we make the decision at compile-time whether it is a type with a trivial assign or not? You guessed it, with type-traits, specifially has_trivial_assign.

#include <type_traits>
#include <cstring>
#include <string>

#include "boolToType.h" // BoolToType helper
// Fast path
template <typename T, typename size_type>
void fast_copy_impl (const T* source, T* destination, const size_type count, const True&)
{
    std::memcpy (destination, source, count * sizeof (T));
}

// Slow path
template <typename T, typename size_type> void fast_copy_impl (const T* source, T* destination, const size_type count, const False&)
{
    for (size_type i = size_type (); i < count; ++i) {
        *destination++ = *source++;
    }
}

template <typename T, typename size_type> void fast_copy (const T* source, T* destination, const size_type count)
{
    BoolToType<std::tr1::has_trivial_assign<T>::value>::type canUseFastPath;
    fast_copy_impl (source, destination, count, canUseFastPath);
}

int main ()
{
    std::string arrComplex [10];
    int arrSimple [20]; // Will call the loop version fast_copy (arrComplex, arrComplex + 5, 5);
    // VC++9 can roll this out to direct copy via registers (!)
    fast_copy (arrSimple, arrSimple + 10, 10);
}

We turn the boolean result of the type-traits into a type by using the helper (as said above, you could directly overload based on integral_constant), and use it to dispatch between two template functions. Voilà , based on the type, we can choose to use memcpy if possible. And the good thing is, this will work for any type. This is something you have no chance to implement on your own as this type-trait relies on (non-portable) compiler support! Another example, how to skip calling T::~T if the destructor does nothing. This can help if you have a large array, and you want to get rid of it quickly - if the destructor is trivial, you can just throw it away, otherwise, you need to clear it. This can be interesting if you have some "scratch-memory" where you allocate blocks from. Here we go:

template <typename T, typename size_type>
void array_delete_impl (T* array, const size_type count, const False&)
{
    for (size_type i = size_type (); i < count; ++i) {
        array[i].~T();
    }
}

template <typename T, typename size_type> void array_delete_impl (T* array, const size_type count, const True&) {}

template <typename T, typename size_type> void array_delete (T*
array, const size_type count)
{
    array_delete_impl (array, count, BoolToType<std::tr1::has_trivial_destructor<T>::value>::type ());
}

Conclusion

Type-traits are a very powerful tool which can both improve the efficiency of template code and improve the maintainability by not relying on custom traits specialisations. The examples here are just very basic - just take a look at how many type traits are available. In addition, there are special type-traits like add_reference which transform types: They add a qualifier if possible (otherwise, you could wind up having a const T& & which is not allowed in current C++ -- will be allowed in C++0x).

Choose the next post subject

Yeah, you, my dear reader: Choose what you want to read about!

  • State of C++0x: What is coming, what is available today already and why it's time to get excited.
  • C++/C/C# Interop: How to consume a C++ app from C# via C - with support for Mono and .NET.
  • Type traits in C++: What they are, how to use them (via Boost or std::tr1), when to use and why they are a useful addition.
  • Movies: I recently watched Street Kings and I'm going to see Ironman on Thursday. If you want, you can get my opinion on these movies :)
  • Your subject idea: Just post a comment with your wish, and I'll take a look at it. You can basically ask about anything. The subjects in my about-section are a good starting point.

Setting up a minimal Debian for VM use

I've been just installing a Linux in a VM again (using VirtualBox, tested with 1.5.6). As a minimal system, I choose Debian (with minimal I mean that you don't get much unused stuff, it boots fast and the disk usage is reasonable. I know that DSL or whatever would be smaller, this is just about getting a decent work system up and running quickly). Here's a small howto:

  1. Get the Debian Net-Installer from Debian.org (180 MiB)
  2. Install debian, when it comes to package setup deselect "Desktop system"
  3. After reboot, install (using apt-get install):

    • x-window-system-core
    • kde-core
    • kdm
    • g++
  4. Reboot

  5. Install the VM additions, but install the kernel headers first using: apt-get install linux-headers-`uname -r`
  6. Reboot, notice that the network doesn't seem to work any longer
  7. Run dhcpd (as root)

Done. This gives you a quite small debian installation which works fine and uses really little disk space (1.10 GiB including the swap-file).

[Update] dhcpd has to be run as root in case no network connection is found.

Bug in Boost 1.35/Threads, Condition::notify_one

There is a bug in Boost.Threads 1.35 (tracked as #1803) -- basically, condition::notify_one may fail to wake-up a thread. It is fixed on the trunk (r44150 -- thanks Anthony Williams for the quick fix). The bug might not sound that serious, however, it can have serious consequences: I have a thread-pool class which uses it, and because of this bug sometimes a new job didn't get scheduled properly as all threads were waiting, and a thread remained sleeping although there was a job with no assigned thread. The good thing is that this thread-pool has been already tested with a different condition implementation before (my own ;) ) and it was also prototyped with Python to check for algorithmic mistakes, so I could quickly track it down to be a Boost issue (first time I found a new bug in Boost, and a quite tricky one even). Anyway, before using Boost.Thread in production applications, you might be better off waiting for 1.35.1 or 1.36. Besides this bug, the library seems to work perfectly, couldn't find any other problem.

[Update] Has been fixed in Boost 1.36.