25.8.13

Type tagging and SFINAE in C++

While it may sound like an onomatopoeia for somebody sneezing, SFINAE is a C++ idiom, standing for 'Substitution Failure Is Not An Error'. The idea is that when instantiating a template, if more than one instantiation is viable, then any other instantiations which would cause an error are not considered. As long as there is one valid instantiation, that instantiation will be used. In other words, the fact that some substitutions may fail is not enough to cause a(n) compilation error. A quick example to demonstrate this—let's assume we have declared the following template which expects to operate on types which contain an embedded type called SomeType:
template<typename T>struct Example {

    typename T::SomeType t;
};

struct Ok { typedef int SomeType; };

Example<Ok> ok; // perfectly fine
It should be fairly uncontroversial to point out that is not going to work with native types, such as int:
Example<int> i; // not so fine
But, if we were to provide an int-compatible instantiation, then the presence of the default instantiation isn't going to interfere with the use of the overridden version:
template<>struct Example<int> {
    int t;
};
Example<int> i; // fine now, default Example template is no longer considered.
This selection process can be used to choose programmatically between different template instantiations, based on the presence or absence of an embedded type (i.e. a tag type) in a type declaration. The syntax used to define these kinds of template mechanisms can often be somewhat opaque, so I devised a mechanism which conveniently wraps the type detection mechanism into a single macro, called TYPE_CHECK(). An example usage would be something like this:
TYPE_CHECK(Test1Check, T, TypeToCheck,
    static const bool VALUE = true, // Test1Check body when T::TypeToCheck exists
    static const bool VALUE = false); // Test1Check body default
This defines a template type called Test1Check<T>, containing a boolean constant VALUE which is true for any T where T::TypeToCheck exists, or false if it doesn't, so, in the following example, we would see output of "0, 1" from printf():
struct TestingF {};
struct TestingT { typedef void TypeToCheck; };

printf("%u, %u\n",  // prints "0, 1"
    Test1Check<TestingF>::VALUE,
    Test1Check<TestingT>::VALUE);
TYPE_CHECK() takes 5 arguments: the first and second are the name of the check type (Test1Check), and the type parameter (usually but not necessarily T). The macro will expand into a template struct definition (template<typename T>struct Test1Check { /*...*/ }; in this case). The third parameter is the name of the type we want to test for (i.e. the presence or absence of T::TypeToCheck), and the fourth and fifth parameters represent the body of this struct (the /*...*/ part) if the test type is present, or the default in the case it's not present.
We could rewrite our initial Example given above as follows, although it should now work for any type without an embedded T::SomeType, and not just int:
TYPE_CHECK(Example, T, SomeType,
    typename T::SomeType t,
    T t);
You can also use TYPE_CHECK() to embed functions into the check type, so that your program can operate differently depending on if the test type is present or not. You can use this to implement some fairly primitive compile-time reflection mechanisms.
One additional refinement worth mentioning is that if you have a compiler which supports C99-style variadic macros, it's possible to parenthesize the fourth and fifth arguments, which is occasionally useful if they need to contain commas—an example of this is in the test code provided below.
There's one additional macro called TYPE_CHECK_FRIEND(). It takes the name of a check defined by TYPE_CHECK() and this can be placed inside the body of a type if you want to give the check access to the internals of a type. Again, there's an example of this in the test code.
The TYPE_CHECK() implementation lives in a single header file, nominally called "type_check.h", which can be copied from here. You should be able to just paste it to a local file and start using it. It contains the two macros outlined above, and a few implementation details (anything in the namespace tc_ or starting with a tc_ prefix), which you can ignore. If you're using a compiler which doesn't support variadic macros, you should #define TYPE_CHECK_NO_VA_ARGS before #including it.
A simple 'test suite' can be copied from here, which shows a few different ways that this kind of mechanism can be used. As far as I'm concerned, this code is public domain, so feel free to do whatever you'd like with it.

Addendum 8.3.14:

I just noticed that my source code (which was hosted on hastebin.com) is no longer available there, so I've pushed the files to Dropbox instead, where hopefully they will remain accessible for the forseeable future. It should make it easier for me to publish updates as well - which is for the best as running the code through ideone reveals an issue with the TYPE_CHECK_FRIEND() macro in GCC 4.8.1, but 4.3.2 seems happy enough with it.

No comments :

Post a Comment