5

I'm trying to implement a template class (named Get<> here) that, given a structure H, the type Get<H>::type is the H itself if the qualified-id H::der doesn't exist, and is Get<H::der>::type otherwise. I can't understand what's wrong with the following code:

#include <iostream>
#include <typeinfo>
using namespace std;

template<class U, class V = void>
struct Get
{
  static const char id = 'A';
  typedef U type;
};

template<class U>
struct Get<U,typename U::der>
{
  static const char id = 'B';
  typedef typename Get<typename U::der>::type type;
};

struct H1
{ };
struct H2
{ typedef double der; };
struct H3
{ typedef void der; };
struct H4
{ typedef H2 der; };

void print(char id, const char* name)
{
  cout << id << ", " << name << endl;
}
int main(int , char *[])
{
  print(Get<H1>::id, typeid(Get<H1>::type).name()); // prints "A, 2H1", OK
  print(Get<H2>::id, typeid(Get<H2>::type).name()); // prints "A, 2H2", why?
  print(Get<H3>::id, typeid(Get<H3>::type).name()); // prints "B, v"  , OK
  print(Get<H4>::id, typeid(Get<H4>::type).name()); // prints "A, 2H4", why?
}

I'd like some help to make this code behave as expected. More specifically, I wish that Get< H2 >::type was equal to double, and the same for Get< H4 >::type.

jonspaceharper
  • 4,207
  • 2
  • 22
  • 42
montefuscolo
  • 259
  • 2
  • 13
  • possible duplicate of [Template specialization to use default type if class member typedef does not exist](http://stackoverflow.com/questions/3008571/template-specialization-to-use-default-type-if-class-member-typedef-does-not-exi) – Emile Cormier Nov 24 '12 at 14:21
  • 2
    litb's `tovoid` trick in this answer solves your problem: http://stackoverflow.com/a/3009891/245265 – Emile Cormier Nov 24 '12 at 14:25
  • nice trick, haven't knew that. – ipc Nov 24 '12 at 14:29

2 Answers2

3

The template Get<> has a default template parameter -- this is very dangerous. Depending on whether V is equal int, void or double you get different results. This is what happens:

Get<H2>::type is Get<H2, void> on the first place (with id='A'). Now comes the check, whether there is an specialization of it. Your B is Get<U,typename U::der> which becomes Get<U, double>. But this doesn't match whith Get<H2, void>, so A is chosen. Things get interesting with Get<H2>::type. Then the variant B is also Get<U, void> and provides a better match. However, the approach can't work for all types.

This is how I would have implemented Get:

template<class U>
class Get
{
  template <typename T, typename = typename T::der>
  static typename Get<typename T::der>::type test(int);
  template <typename T>
  static T test(...);
public:
  typedef decltype(test<U>(0)) type;
};
ipc
  • 8,045
  • 29
  • 33
2

While I give a +1 to @ipc answer and it is very well for C++11 enabled compilers, for C++03 compilers you should use a different approach, since default value for template arguments of a function is not supported in C++03. So I have this code:

template<class U, class V = void>
struct Get
{
    static const char id = 'A';
    typedef U type;
};

template< class T >
struct is_type {
    static const bool value = true;
};

template<class U>
struct Get<U,
    typename std::tr1::enable_if<is_type<typename U::der>::value, void>::type>
{
    static const char id = 'B';
    typedef typename Get<typename U::der>::type type;
};
BigBoss
  • 6,904
  • 2
  • 23
  • 38
  • If you are using `boost::enable_if` (`std::enable_if` is C++11), `typename boost::enable_if::type` should work too and makes the `is_type` helper class superfluous. – ipc Nov 24 '12 at 14:39
  • @ipc Actually I'm using `boost::enable_if` in my real project but it is not like that, since condition of `boost::enable_if` should be an `mpl` boolean constant. correct code is `boost::enable_if >`. And please remember `std::enable_if` belong to `TR1` not C++11. – BigBoss Nov 24 '12 at 14:43
  • ok, you're right. But `std::enable_if` still is C++11 since embedding `std::tr1` in `std` is nonstandard. – ipc Nov 24 '12 at 14:46
  • @ipc Sorry I don't get it, document you addressed certainly describe the `Cond` argument and it say it should be a type with a nested constant bool named `value`(This is a boolean constant in `MPL`). [2 The enable_if templates](http://www.boost.org/doc/libs/1_52_0/libs/utility/enable_if.html#htoc4) – BigBoss Nov 24 '12 at 14:51
  • @ipc thanks for the comment, I correct non-standard usage of `std::tr1::enable_if` in `std` namespace – BigBoss Nov 24 '12 at 14:54