Характеристики типа С++ для выбора между T1 и T2

Я хочу, чтобы шаблон выбирался из двух типов на основе некоторого условия. Например.

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;
};

Конечно, передать условие select_base (чтобы сделать его общим) было бы полезно, но жестко запрограммированное решение было бы проще и лучше.

Вот пример решения, которое я пробовал, но он всегда выбирает T1: http://ideone.com/EnVT8

Вопрос заключается в том, как реализовать шаблон select_base.

Ответ 1

С++ 14 (и далее):

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

В том же духе вы можете использовать это:

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

Различие между этими двумя подходами может наблюдаться при их использовании. Например, в первом случае, если вам нужно использовать ::type, тогда как во втором - нет. И если какой-либо зависимый тип связан с использованием первого подхода, вы также должны использовать typename, чтобы помочь компилятору. Второй подход свободен от всех таких шумов и, таким образом, превосходит остальные подходы в этом ответе.

Также обратите внимание, что вы можете написать аналогичный псевдоним типа в С++ 11.


С++ 11:

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

С++ 98:

Условное достаточно просто:

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 немного сложнее, в Boost доступна реализация, которую я не буду воспроизводить здесь.

Затем, см. С++ 11.

Ответ 2

Если вы используете std::conditional вместо шаблона класса if_, как это было реализовано в @Matthieu в его ответе, тогда ваше решение уменьшится до это:

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

Или просто это:

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

который выглядит еще лучше.

Разница между этими двумя решениями заключается в том, что в первом решении вы даете программирующее имя вложенному типу, так как я дал ему base_type, а во втором решении вложенный тип - это просто type который не выглядит более удобным для программистов.

Обратите внимание, что в обоих приведенных выше решениях вы должны использовать вложенный тип как select_base<T,U>::base_type (в первом решении) или select_base<T,U>::type (во втором решении — и из-за этого, ve используйте typename, как вы сами написали в самом вопросе.

Однако, если вы вместо этого используете псевдоним шаблона, который определяется как:

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

то вы можете использовать base_type<T,U> без каких-либо вложенных типов и typename как:

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

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

Надеюсь, что это поможет.