Andrew Spott
Andrew Spott

Reputation: 3637

Getting a typedef of a template parameter, or failing that, getting the type itself

I am attempting to get the type pointed to by a typedef from a class (the ValueType typedef) that I defined. However, when this fails, I want it to return the type that is given (for example if I give it a double, I want it to return a double). This is what I have so far:

struct myClass { typedef double ValueType; };

template < typename V, typename U = typename V::ValueType>
struct base_type { typedef U type; };
template < typename V >
struct base_type< V, V > { typedef V type; };

static_assert( std::is_same < typename base_type< myClass >::type , double >::value, 
    "base_type doesn't work" ); //This works.
static_assert( std::is_same < typename base_type< double >::type , double >::value,
    "base_type doesn't work" ); //This returns "error: 'double' is not a class, struct, or union type"

However, this doesn't work, the second static_assert fails. Obviously, the second definition is never called, but I'm not sure why (it will definitely match better than the first).

Any ideas?

Upvotes: 3

Views: 705

Answers (5)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275470

#include <type_traits>
#include <utility>

template < typename V, typename=void>
struct base_type { typedef V type; };
template < typename V >
struct base_type<
  V,
  typename std::enable_if<
    std::is_same<
      typename V::ValueType,
      typename V::ValueType
    >::value
  >::type
>
{
  typedef typename V::ValueType type;
};

This is a pretty generic technique. The base case has an extra unnamed template parameter defaulting to void.

The specialization does a typename std::enable_if< expression >::type in place of that void, and it is a valid specialization IFF that expression is both valid and true.

In this case, I did a simple std::is_same< V::value_type, V::value_type >::value. In my own code, I've been known to write valid_type< typename V::value_type >::value, which is simply:

template<typename>
struct valid_type:std::true_type {};

an always-true traits class.

Now, the void trick has some issues. But I find it nicely generic.

Upvotes: 2

Johannes Schaub - litb
Johannes Schaub - litb

Reputation: 507005

Alternatively

template < typename V, typename = void>
struct base_type { typedef V type; };
template < typename V >
struct base_type< V, 
                 typename base_type<void, typename V::ValueType>::type > { 
    typedef typename V::ValueType type; 
};

How we all love these crude and ugly SFINAE hacks :)

Upvotes: 5

Konrad Rudolph
Konrad Rudolph

Reputation: 545608

double won’t instantiate your specialisation because that specialisation only gets invoked when base_type gets passed two types and both are identical. The second type is optional but when it’s not provided it’s filled in with the default typename V::ValueType and this of course fails for double::ValueType.

For completeness’ sake, your base_type<V, V> specialisation would get instantiated for the following type:

struct foo { typedef foo ValueType; };

static_assert(std::is_same<base_type<foo>::type, foo>::value, "Failed");

Upvotes: 1

ipc
ipc

Reputation: 8143

You have to use a function for effective SFINAE.

Here is what this looks like with the new C++11 features:

template <typename T> auto get_base_type(int) -> typename T::ValueType;
template <typename T> auto get_base_type(...) -> T;

template <typename T>
struct base_type { using type = decltype(get_base_type<T>(0)); };

Upvotes: 5

EHuhtala
EHuhtala

Reputation: 587

from wikipedia The following works for me, and I think it does what you want.

#include <type_traits>

template <typename T>
struct has_typedef_ValueType {
// Types "yes" and "no" are guaranteed to have different sizes,
// specifically sizeof(yes) == 1 and sizeof(no) == 2.
typedef char yes[1];
typedef char no[2];

template <typename C>
static yes& test(typename C::ValueType*);

template <typename>
static no& test(...);

// If the "sizeof" the result of calling test<T>(0) would be equal to the sizeof(yes),
// the first overload worked and T has a nested type named foobar.
static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};

struct myClass { typedef double ValueType; };

template < class V, bool b = has_typedef_ValueType<V>::value >
struct base_type { typedef typename V::ValueType type; };
template < typename V>
struct base_type <V, false> { typedef V type; };

static_assert( std::is_same < typename base_type< myClass >::type , double >::value,     "base_type doesn't work" ); //This works.
static_assert( std::is_same < typename base_type< double >::type , double >::value, "base_type doesn't work" ); //This returns "error: 'double' is not a class, struct, or     union type"

int main() {}

Upvotes: 2

Related Questions