Поиск первого непустого подтипа в аргументах шаблона

#c #templates #recursion #variadic-templates #template-meta-programming

#c #шаблоны #рекурсия #переменные-шаблоны #шаблон-метапрограммирование

Вопрос:

Я пытаюсь написать метафункцию C , которая возвращает мне первый непустой подтип для предоставленных параметров шаблона.

Например:

 struct I { using subtype = int; };
struct D { using subtype = double; };
struct E { using subtype = empty ; };
  

Я пытаюсь достичь:

 static_assert(std::is_same<int, first_non_empty_subtype<E,E,I>>::value, "the first non-empty subtype should be 'int'");
static_assert(std::is_same<double, first_non_empty_subtype<E,D,I>>::value, "the first non-empty subtype should be 'double'");
static_assert(std::is_same<empty, first_non_empty_subtype<E,E,E>>::value, "since all subtypes are empty, the result is empty");
  

Мои первоначальные мысли заключаются в использовании std::conditional_t с рекурсией шаблона:

 template <typename T, typename ...Ts>
using first_non_empty_subtype = std::conditional_t<
    !std::is_empty<typename T::subtype>::value, 
    typename T::subtype, 
    first_non_empty_subtype<Ts...>>::type
  

Однако я не совсем знаком с реализацией рекурсии шаблона для псевдонимов типов.

Может кто-нибудь помочь мне указать правильное направление для решения этой проблемы?

Спасибо!

Комментарии:

1. Чтобы рекурсия работала, вам нужно что-то, что прерывает рекурсию. В вашем примере такой вещи нет, и AFAIK это невозможно сделать с помощью псевдонимов шаблонов, вы никак не можете их специализировать. Вы можете создать рекурсивную структуру, которая содержит только using subtype = ... и использовать это. Если вы хотите, вы можете создать псевдоним шаблона, который указывает на рекурсивную структуру для упрощения использования.

2. Спасибо, что указали на это! Я намеренно опустил это, потому что не был уверен, как это будет реализовано. Спасибо за объяснение того, что псевдонимы шаблонов не могут быть специализированными — @max66 показывает решение, использующее рекурсивную структуру.

Ответ №1:

Я предлагаю что-то вроде следующего

 // ground case: no more types, so empty
template <typename ...>
struct fnes_helper
 { using type = empty; };

// the first type is T and isn't empy; so T
template <typename T, typename ... Ts>
struct fnes_helper<T, Ts...>
 { using type = T; };

// the first type is empty; so recursion
template <typename ... Ts>
struct fnes_helper<empty, Ts...> : public fnes_helper<Ts...>
 { };

template <typename ... Ts>
using first_non_empty_subtype 
   = typename fnes_helper<typename Ts::subtype...>::type;
  

Обратите внимание, что fnes_helper более специализированная версия — это та, в которой empty тип находится на первой позиции, поэтому в этом случае используется версия.
Далее следует другая специализация, с универсальным T типом на первой позиции, и, наконец, у нас есть основная версия, которая выбирается в других случаях, поэтому список типов пуст.

Также не забудьте добавить {} или ::value после std::is_same в static_assert() тестах

 static_assert(std::is_same<int, first_non_empty_subtype<E,E,I>>{},
              "the first non-empty subtype should be 'int'");
static_assert(std::is_same<double, first_non_empty_subtype<E,D,I>>{},
              "the first non-empty subtype should be 'double'");
static_assert(std::is_same<empty, first_non_empty_subtype<E,E,E>>{},
              "since all subtypes are empty, the result is empty");
  

Комментарии:

1. Круто, спасибо! Это было то, что я искал. Спасибо за быстрый ответ и за то, что нашли время объяснить, как работают решения.