Peter Awesome
Peter Awesome

Reputation: 59

Access type member

In my example I have a class Foo<T>. In my function test I need to get the template parameter of Foo otherwise the normal type. First I started to use std::conditional but forgot that the template parameters must all be valid, no matter which one is picked. Is the only way to create a type-specialisation for non-Foo types?

Example

#include <type_traits>

template <typename TYPE>
class Foo
{
public:
  using M = TYPE;
};

template <typename T>
void test(const T& a)
{
  // actually I would have used !is_foo<T>::value for the first arg
  // but this check is fine to minimise the example
  using MY_TYPE = typename std::conditional<
    std::is_same<T, int>::value,
    T,
    typename T::M>::type; // <---Error: error: type 'int' cannot be used prior to '::' because it has no members
}

int main()
{
  test(Foo<int>()); // MY_TYPE must be int
  test(int()); // MY_TYPE must be int
  return 0;
}

Upvotes: 2

Views: 152

Answers (3)

Rostislav
Rostislav

Reputation: 3977

You can do some void_t magic to allow SFINAE to figure help you out:

#include <type_traits>
#include <iostream>
#include <typeinfo>

template <typename TYPE>
class Foo
{
public:
  using M = TYPE;
};

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

// primary template handles types that have no nested ::T member:
template< class T, class = void_t<> >
struct M_or_T { using type = T; };

// specialization recognizes types that do have a nested ::T member:
template< class T >
struct M_or_T<T, void_t<typename T::M>> { using type = typename T::M; };


template <typename T>
void test(const T& a)
{
    using MY_TYPE = typename M_or_T<T>::type;
    std::cout << typeid(MY_TYPE).name() << "\n";
}

int main()
{
  test(Foo<int>()); // MY_TYPE must be int
  test(int()); // MY_TYPE must be int
  return 0;
}

What happens is that the second overload of M_or_T substitution fails for int (and for any type without a type member M) and thus the first overload is chosen. For types which have a type member M, a more specialized second overload is chosen.

Upvotes: 2

David Stocking
David Stocking

Reputation: 1212

#include <type_traits>

template <typename TYPE>
class Foo
{
public:
  using M = TYPE;
};

template <typename T>
void test(const Foo<T>& a)
{
    using MY_TYPE = Foo<T>::M;
    testOther<MY_TYPE>(a);
}

template <typename T>
void test(const T& a)
{
    using MY_TYPE = T;
    testOther<MY_TYPE>(a);
}

template <typename T, typename S>
void testOther(const S& a)
{
    // do stuff
}

int main()
{
  test(Foo<int>()); // MY_TYPE must be int
  test(int()); // MY_TYPE must be int
  return 0;
}

I'm not exactly sure what you wanted, but I hope this is what you wanted. It might be a bit off. I didn't compile this.

Upvotes: 0

TartanLlama
TartanLlama

Reputation: 65620

Well you could make an UnFoo helper to get the right type for you:

template <typename T>
struct UnFoo {
    using type = T;
};

template <typename T>
struct UnFoo<Foo<T>> {
    using type = T;
};

template <typename T>
void test(const T& a)
{
  using MY_TYPE = typename UnFoo<T>::type; //maybe with a helper to get rid of typename
}

Another option would be to write an overload for Foo<T> and have it delegate to the other function, but that depends on what your real test function does.

Upvotes: 5

Related Questions