Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ type traits to select between T1 and T2

I want a template to select from two types based on some condition. E.g.

struct Base {};

template <typename T1, typename T2>
struct test
{
    // e.g. here it should select T1/T2 that is_base_of<Base>
    typename select_base<T1, T2>::type m_ValueOfBaseType;
};

Of course to pass condition to the select_base (to make it generic) would be useful, but hard-coded solution is easier and good as well.

Here's a sample solution that I tried but it always selects T1: http://ideone.com/EnVT8

The question is how to implement the select_base template.

like image 641
queen3 Avatar asked Jun 16 '12 16:06

queen3


2 Answers

If you use std::conditional instead of if_ class template as implemented by @Matthieu in his answer, then your solution would reduce to this:

template <typename T, typename U>
struct select_base
{
   typedef typename std::conditional<std::is_base_of<T, Base>::value, T, U>::type base_type;
};

Or simply this:

template <typename T, typename U>
struct select_base : std::conditional<std::is_base_of<T, Base>::value, T, U> {};

which looks even better.

The difference between these two solutions is that in the first solution you give a programmer-friendly name to the nested type, as I've given it base_type, while in the second solution the nested type is just type which doesn't look that programmer-friendly.

Note that in both of the above solutions, you've to use the nested type as either select_base<T,U>::base_type (in the first solution) or select_base<T,U>::type (in the second solution — and because of that, if you've use typename as you've written yourself in the question itself.

However, if you instead use template alias, defined as:

template<typename T, typename U>
using base_type = typename std::conditional<std::is_base_of<T, Base>::value, T, U>::type;

then you can use base_type<T,U> without any nested-type and typename as:

template <typename T1, typename T2>
struct test
{
   //typename select_base<T1, T2>::type m_ValueOfBaseType; //ugly!

   base_type<T1, T2>  m_ValueOfBaseType; //better
};

Hope that helps.

like image 95
Nawaz Avatar answered Oct 15 '22 01:10

Nawaz


C++14 (and onwards):

template <typename T, typename U>
struct select_base:
    std::conditional_t<std::is_base_of<T, Base>::value, T, U> {};

In the same vein, you can instead use this:

template<typename T, typename U>
using select_base = std::conditional_t<std::is_base_of_v<T,Base>, T, U>;

The difference between these two approaches can be observed when you use them. For example, in the first case if you have to use ::type whereas in the second, you dont. And if any dependent type involves in the usage of the first approach, you have to use typename as well, to assist the compiler. The second approach is free of all such noises and thus superior to the rest of the approaches in this answer.

Also, please note that you can write similar type-alias in C++11 as well.


C++11:

template <typename T, typename U>
struct select_base:
    std::conditional<std::is_base_of<T, Base>::value, T, U>::type {};
//                 ^                                       ^~~~~~

C++98:

Conditional is easy enough:

template <typename bool, typename T, typename U>
struct conditional { typedef T type; };

template <typename T, typename U>
struct conditional<false, T, U> { typedef U type; };

is_base_of is slightly more complicated, an implementation is available in Boost that I will not reproduce here.

Afterwards, see C++11.

like image 22
Matthieu M. Avatar answered Oct 15 '22 02:10

Matthieu M.