Interface Traits

Motivation

Suppose you have an algorithm to iterate over a range of iterators, and pass the dereferenced iterator to a policy object supplied by the user:

   1 template<typename I, typename P>
   2 void for_each(I begin, const I& end, P& policy) {
   3   for(; begin != end; ++begin)
   4     policy.apply(*begin);
   5 }

Youre happy and start using for_each() everywhere in your code.

But every now and when you notice that it would be nice the to have the iteration counter available in the policy object as well. Well, that is easy to implement:

   1 template<typename I, typename P>
   2 void for_each(I begin, const I& end, P& policy) {
   3   for(unsigned i = 0; begin != end; ++begin, ++i)
   4     policy.apply(*begin, i);
   5 }

But now all your old policy objects stopped working!

It would be a pity if you had to change all the policy classes. And think of all the people using your library -- do you really want to force them to change all their policy classes as well? That can be a real nightmare!

Wouldn't it be nice to support both one- and two-argument apply() methods in the policy objects? Turns out that it's possible, but complicated.

Basic Technique

It is easy to check whether a class has a certain type member or a certain compile-time constant using the SFINAE technique and enable_if. To illustrate this we are going to need some basic classes:

   1 template<typename T, T t>
   2 struct integral_constant {
   3   typedef integral_constant<T, t> type;
   4   typedef T value_type;
   5   static const T value = t;
   6 };
   7 typedef integral_constant<bool, false> false_type;
   8 typedef integral_constant<bool, true>  true_type;
   9 
  10 template<bool, typename = void> struct enable_if { };
  11 template<typename T> struct enable_if<true, T> { typedef T type; };
  12 
  13 template<bool, typename, typename F>
  14 struct conditional             { typedef F type; };
  15 template<typename T, typename F>
  16 struct conditional<true, T, F> { typedef T type; };

All of them are also available in the upcomming C++ standard or from Boost.

The check whether some class has a member type can be written this way:

   1 template<typename> struct always_void { typedef void type; };
   2 
   3 template<typename, typename = void>
   4 struct has_type_member_foo : public false_type { };
   5 template<typename T>
   6 struct has_type_member_foo<T, typename conditional<false, typename T::foo, void>::type>
   7 : public true_type { };

The check whether some class has a compile-time constant can be written this way:

   1 template<typename, typename = void>
   2 struct has_compiletime_constant_bar : public false_type { };
   3 template<typename T>
   4 struct has_compiletime_constant_bar<T, typename conditional<T::bar, void, void>::type>
   5 : public true_type { };

It is even possible to check for static or non-static member variables:

   1 template<typename, typename = void>
   2 struct has_static_member_var_baz : public false_type { };
   3 template<typename T>
   4 struct has_static_member_var_baz<T, typename enable_if<sizeof T::baz>::type>
   5 : public true_type { };
   6 
   7 template<typename, typename = void>
   8 struct has_nonstatic_member_var_baz : public false_type { };
   9 template<typename T>
  10 struct has_nonstatic_member_var_baz<T, typename enable_if<sizeof static_cast<T*>(0)->baz>::type>
  11 : public true_type { };

Here we take advantage of the fact that every variable has a size bigger than one, so sizeof will never return 0, so its return value will always be interpreted as true. We also used a mock object *static_cast<T*>(0). This may look like we're dereferencing a null-pointer here -- however, expressions which are an argument to the sizeof operator never actually get evaluated, so this is OK.

Checking for member functions

The last traits class has_nonstatic_member_var_baz can be extended to work For any expression involving a member function call on T. To return to our origninal example to check whether we can call the two argument version of apply(), we an use the following traits class:

   1 template<typename P, typename I, typename = void>
   2 struct has_newstyle_apply : public false_type { };
   3 template<typename P, typename I>
   4 struct has_newstyle_apply<P, I, typename enable_if<
   5     sizeof static_cast<P*>(0)->apply(**static_cast<I*const>(0), 0u)
   6   >::type>
   7 : public true_type { };

Here we use another mock object *static_cast<I*const>(0) for the iterator and dereference it to get an expression of the type that dereferencing a real interator would yield. The dummy value for the second argument is much easier to invent, wie just use the numerical value 0 and make sure it has type unsigned.

To see how this works out, we test this with one oldstyle and one newstyle policy class:

   1 #include <iostream>
   2 
   3 struct OldPolicy {
   4   template<typename T> int apply(const T&);
   5 };
   6 struct NewPolicy {
   7   template<typename T> int apply(const T&, unsigned long) const;
   8 };
   9 
  10 int main() {
  11   std::cout << std::boolalpha
  12             << "OldPolicy has newstyle apply: "
  13             << has_newstyle_apply<OldPolicy, int*>::value << std::endl
  14             << "NewPolicy has newstyle apply: "
  15             << has_newstyle_apply<NewPolicy, int*>::value << std::endl;
  16 }

The full test program is available as test1.cc.

The program output confirms that this works indeed:

OldPolicy has newstyle apply: false
NewPolicy has newstyle apply: true

Handling the return type void

You may have noticed that I sneakily used int as return type of the apply() methods of the policy classes. Normally one would like to use void, since the return value of apply is not used. Let's try it out:

   1 struct OldPolicy {
   2   template<typename T> void apply(const T&);
   3 };
   4 struct NewPolicy {
   5   template<typename T> void apply(const T&, unsigned long) const;
   6 };

The full test program is available as test2.cc. The result is rather discouraging:

OldPolicy has newstyle apply: false
NewPolicy has newstyle apply: false

What has happened here is that we have given an expression of type void to the sizeof operator. According to the standard, sizeof shall not be applied to an incomplete type, and the void type can never be completed.

So we need a way to turn a void expression into a non-void expression. The standard lists exactly three operators which can take void expressions as arguments: , (comma operator), ?:, and typeid. ?: is useless, since either non or both of the second and third arguments have to be void, or the void argument has to be a throw expression. typeid should work, though I didn't manage to actually make it work and didn't investigate very far. The comma operator , should suit us fine: We just give it the void expression as its left operand and use some arbitrary non-void expression as the right operand. The resulting expression will always have type and value of the right operand.

   1 template<typename P, typename I, typename = void>
   2 struct has_newstyle_apply : public false_type { };
   3 template<typename P, typename I>
   4 struct has_newstyle_apply<P, I, typename enable_if<sizeof(
   5     static_cast<P*>(0)->apply(**static_cast<I*const>(0), 0u),
   6   0)>::type>
   7 : public true_type { };

The full test program is available as test3.cc. The result looks good again:

OldPolicy has newstyle apply: false
NewPolicy has newstyle apply: true

Dealing With Compiler Bugs

Now, when I was first using this technique, I wanted to be nice to my users. If the policy classes are somehow derived from base classes implementing some of the functionality and some of the base classes are migrated to the new interface, it is possible to end up with a policy class that has both the oldstyle and the newstyle apply(), each doing some and none doing all of the work. So I wanted to check for this by giving my users a compile time error in this situation. With static_assert from the upcoming C++ standard this compile time error could even be accompanied by a nice error message.

So I made another traits class to check for the oldstyle apply() and put it in front of the definition for has_newstyle_apply:

   1 template<typename P, typename I, typename = void>
   2 struct has_oldstyle_apply : public false_type { };
   3 template<typename P, typename I>
   4 struct has_oldstyle_apply<P, I, typename enable_if<sizeof(
   5     static_cast<P*>(0)->apply(**static_cast<I*const>(0)),
   6   0)>::type>
   7 : public true_type { };

The full test program is available as test4.cc. The result was rather strange:

OldPolicy has oldstyle apply: true
OldPolicy has newstyle apply: true
NewPolicy has oldstyle apply: false
NewPolicy has newstyle apply: false

Even more strangely, simply reversing the order of definition for has_oldstyle_apply and has_newstyle_apply in the source (test5.cc) would invert the result:

OldPolicy has oldstyle apply: false
OldPolicy has newstyle apply: false
NewPolicy has oldstyle apply: true
NewPolicy has newstyle apply: true

That was when I knew it would pay off to download and install the package gcc-snapshot. And indeed, with the new svn snapshot of gcc and test4.cc, I got the expected result:

OldPolicy has oldstyle apply: true
OldPolicy has newstyle apply: false
NewPolicy has oldstyle apply: false
NewPolicy has newstyle apply: true

So, this is indeed a compiler bug.

The compiler I was working with orignially was g++-4.4. The snapshot was some svn version of g++-4.6.

One workaround to this appears to be to give the right argument of the comma operator different values in the two traits classes:

   1 template<typename P, typename I, typename = void>
   2 struct has_oldstyle_apply : public false_type { };
   3 template<typename P, typename I>
   4 struct has_oldstyle_apply<P, I, typename enable_if<sizeof(
   5     static_cast<P*>(0)->apply(**static_cast<I*const>(0)),
   6   0)>::type>
   7 : public true_type { };
   8 
   9 template<typename P, typename I, typename = void>
  10 struct has_newstyle_apply : public false_type { };
  11 template<typename P, typename I>
  12 struct has_newstyle_apply<P, I, typename enable_if<sizeof(
  13     static_cast<P*>(0)->apply(**static_cast<I*const>(0), 0u),
  14   1)>::type>
  15 : public true_type { };

(see test6.cc). This appears to work with g++-4.4.

g++-4.3 would not even compile the code:

g++-4.3 -ansi -pedantic -Wall test4.cc -o test4 && ./test4
test4.cc: In function 'int main()':
test4.cc:45: error: no matching function for call to 'NewPolicy::apply(int&)'
test4.cc:47: error: no matching function for call to 'NewPolicy::apply(int&)'

What can we do about it? The

On Member Function Pointers

Most of the existing techniques to check for member functions use a member function pointer. They have one of two disadvantages: Either they require the complete signature of the member function to check for, including the return type. Or they do not check the signature at all and work only when the member function is not overloaded. This is all rather unsatisfying, since we usually want to know whether we can call a member function in a certain way

Jö: InterfaceTraits (last edited 2010-07-11 21:14:18 by )