Wednesday, August 17, 2011

SFINAE and type trait-based overloading, take two

Today I thought back about my previous post on SFINAE and found that this has_const_iterator dichotomy was actually quite ugly and ineffective.

First, the print_r function has two overloads based on if the argument has a const_iterator or not. In a real-world implementation, we would also check for operator[], iterator, and also the C++11-like begin() and end(), because in fact you could name you iterators whatever you like to. One should also take into account that other special types may need special treatement, like smart pointers. Are we going to add an argument to print_r() for each type we want to support ?

Secondly, the fact that there is no default specialization of the template make it looks poorly TMP-ish. Also, that char(*)[!(has_const_iterator::value)] = 0 is really ugly.

But the real problem is not here. It is that the struct has_const_iterator is actually of no use at all. One could just use the type traits directly as function arguments with default values :
template <typename T> void print_r(const T& v, 
    typename T::const_iterator* x = 0)
and
template <typename T> void print_r(const T& t, ...)
We can make a begin()/end() version by using some C++11 features :
template <typename T> void print_r(const T& v,
    decltype(v.begin())* x = 0, decltype(v.end())* y = 0) {
  // ...blah...
  for(auto iter = v.begin(); iter != v.end(); iter++) {
    // etc.
Wow, this is getting nicer !
Now the problem is that using decltypes and default arguments like that seem not to please the compilers too much. If you add other overloads, GCC will complain based on how you place the different templates, and MSVC won't know how to choose between the different 1 overloads(yes, really). We could fix that by using trait types and a set of capability-detecting functions. But I'll do that later.

You may want to follow me on twitter.

No comments:

Post a Comment