tags:

views:

88

answers:

3

Hi Everyone,

I'm trying to write code that uses a member typedef of a template argument, but want to supply a default type if the template argument does not have that typedef. A simplified example I've tried is this:

struct DefaultType    { DefaultType()    { printf("Default ");    } };
struct NonDefaultType { NonDefaultType() { printf("NonDefault "); } };

struct A {};
struct B { typedef NonDefaultType Type; };

template<typename T, typename Enable = void> struct Get_Type { 
    typedef DefaultType Type; 
};
template<typename T> struct Get_Type< T, typename T::Type > {
    typedef typename T::Type  Type; 
};

int main()
{
    Get_Type<A>::Type test1;
    Get_Type<B>::Type test2;
}

I would expect this to print "Default NonDefault", but instead it prints "Default Default". My expectation is that the second line in main() should match the specialized version of Get_Type, because B::Type exists. However, this does not happen.

Can anyone explain what's going on here and how to fix it, or another way to accomplish the same goal?

Thank you.

Edit:

Georg gave an alternate method, but I'm still curious about why this doesn't work. According the the boost enable_if docs, a way to specialize a template for different types is like so:

template <class T, class Enable = void> 
class A { ... };

template <class T>
class A<T, typename enable_if<is_integral<T> >::type> { ... };

template <class T>
class A<T, typename enable_if<is_float<T> >::type> { ... };

This works because enable_if< true > has type as a typedef, but enable_if< false > does not.

I don't understand how this is different than my version, where instead of using enable_if I'm just using T::Type directly. If T::Type exists wouldn't that be the same as enable_if< true >::type in the above example and cause the specialization to be chosen? And if T::Type doesn't exist, wouldn't that be the same as enable_if< false >::type not existing and causing the default version to be chosen in the above example?

+4  A: 

You can do that by utilizing SFINAE:

template<class T> struct has_type {
    template<class U> static char (&test(typename U::Type const*))[1];
    template<class U> static char (&test(...))[2];
    static const bool value = (sizeof(test<T>(0)) == 1);
};

template<class T, bool has = has_type<T>::value> struct Get_Type {
    typedef DefaultType Type;
};

template<class T> struct Get_Type<T, true> { 
    typedef typename T::Type Type;
};
Georg Fritzsche
Thanks George, that works. I edited my question with another question about why my implementation doesn't work, because it seems like the same principle that enable_if itself is based on. enable_if<true> has a typedef, but enable_if<false> does not. I don't understand why that isn't the same as whether T::Type exists or not.
Frank
@Frank: Johannes already answered that nicely in another answer.
Georg Fritzsche
+3  A: 

First step: stop using "Type" and use the mpl standard "type".


BOOST_MPL_HAS_XXX_DEF(Type)

template < typename T >
struct get_type { typedef typename T::Type type; };

template < typename T >
struct calculate_type : boost::mpl::if_
<
  has_Type<T>
, get_type<T>
, boost::mpl::identity<default_type>
>::type {}

typedef calculate_type<A>::type whatever;

If you used "type" instead of "Type" in your metafunctions you wouldn't require the fetcher "get_type" to convert it and could just return T in that case.

Noah Roberts
+1  A: 

To answer your addition - your specialization argument passes the member typedef and expects it to yield void as type. There is nothing magic about this - it just uses a default argument. Let's see how it works. If you say Get_Type<Foo>::type, the compiler uses the default argument of Enable, which is void, and the type name becomes Get_Type<Foo, void>::type. Now, the compiler checks whether any partial specialization matches.

Your partial specialization's argument list <T, typename T::Type> is deduced from the original argument list <Foo, void>. This will deduce T to Foo and afterwards substitutes that Foo into the second argument of the specialization, yielding a final result of <Foo, NonDefaultType> for your partial specialization. That doesn't, however, match the original argument list <Foo, void> at all!

You need a way to yield the void type, as in the following:

template<typename T>
struct tovoid { typedef void type; };

template<typename T, typename Enable = void> struct Get_Type { 
    typedef DefaultType Type; 
};
template<typename T> 
struct Get_Type< T, typename tovoid<typename T::Type>::type > {
    typedef typename T::Type  Type; 
};

Now this will work like you expect. Using MPL, you can use always instead of tovoid

typename apply< always<void>, typename T::type >::type
Johannes Schaub - litb
Thanks Johannes. I didn't realize that the type itself had to be void to match, but that makes perfect sense.
Frank