1

I want to use the same class template name for two (or more) different implementations, depending on some property of the typename T argument.

My first attempt:

template< typename T > class invert;

template< typename port >
class invert< port > : public port {
public:
   static int n_pins(){ return port::n_pins(); }
   static void set( unsigned int x ){ port_out::set( x ^ 0xFFFF ); }
};

template< typename pin >
class invert< pin > : public pin {
public:
   static void set( bool x ){ pin::set( ! x ); }
};

I hoped the first specialization would be selected when a T::n_pins() was available, but the compiler complains my specializations do not specialize any template argument.

Is there another way I can specialize on a property of the typename argument?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304

1 Answers1

3

Why your code didn't work

The template parameters port and pin are just formal parameters. E.g. for regular functions, would you expect that

void foo(int x) { /* bla */ }

and

void foo(int y) { /* meh */ }

to be different from each other? In fact, you can simply declare such functions in a header by their signature void foo(int) only. To overload functions you need to provide different arguments.

With template parameters, it works exactly the same

template<typename port> class invert { /* bla */ };

and

template<typename pin> class invert { /* bla */ }; 

have the same template signature and can in fact be declared as template<typename> class invert; inside a header.

Here, the analogy breaks down because class templates don't overload, but have to be specialized. This means that your two versions of invert need to be "different enough" in their parameter structure, regardless of what you write in the class implementation itself.

How to fix it

To get what you want, you actually need to specialize at least one of the tempaltes. The typical way to do that has been explained on here before, and is to define a traits class and use SFINAE to select at compile-time:

template <typename T>
class has_n_pins
{
    typedef char one;
    typedef long two;

    template <typename C> static one test( typeof(&C::n_pins) ) ;
    template <typename C> static two test(...);


public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

template<typename T, bool = has_n_pins<T>::value >
class invert: public T
{
    // your current port implementation
};

// specialization for types T that don't have a n_pins() member function
template<typename T>
class invert<T, false>: public T
{
     // your current pin implementation
};

Now there are two specializations: invert<T, true> (all T that have n_pins()) and invert<T, false> (all T that don't). The compiler will now be able to select the appropriate one depending on the template argument you actually provided.

Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Thanks, it works fine (after replacing typeof with decltype), now I'll start trying to understand it :) http://www.gockelhut.com/c++/articles/has_member seems explain this. – Wouter van Ooijen Jun 10 '13 at 07:51
  • you can read about it in [Modern C++ Design](http://www.amazon.com/Modern-Design-Generic-Programming-Patterns/dp/0201704315). The trick is to do overload resolution on `test()` for two types of arguments. The `...` argument has the lowest precedence, so if there is no `n_pins()` member, the `sizeof(test(0))` will return a `long`, otherwise a `char`. – TemplateRex Jun 10 '13 at 08:17
  • I got the book (2 days ago). I can read the words in your explanation, but don't fully comprehend it yet (don't worry, I'll will). – Wouter van Ooijen Jun 10 '13 at 08:55
  • @WoutervanOoijen It's explained in section 2.7 – TemplateRex Jun 10 '13 at 09:01