Проверьте, содержит ли пакет параметров C++0x тип

Мне было интересно, если C++0x предоставляет какие-либо встроенные возможности, чтобы проверить, содержит ли пакет параметров шаблона Variadic определенного типа. Сегодня, boost::: mpl:: contains может использоваться для достижения этой цели, если вы используете boost:: mpl:: vector в качестве замены собственно шаблонов variadic. Тем не менее, он имеет серьезные накладные расходы во время компиляции. Я полагаю, C++0x имеет поддержку уровня компилятора для std::is_same. Поэтому я подумал, что в компиляторе также поддерживается обобщение, подобное приведенному ниже.

template <typename... Args, typename What>
struct is_present
{
  enum { value = (What in Args...)? 1 : 0 };
};

4 ответа

Решение

Нет, вы должны использовать (частичную) специализацию с переменными шаблонами для выполнения вычислений во время компиляции следующим образом:

#include <type_traits>

template < typename Tp, typename... List >
struct contains : std::true_type {};

template < typename Tp, typename Head, typename... Rest >
struct contains<Tp, Head, Rest...>
: std::conditional< std::is_same<Tp, Head>::value,
    std::true_type,
    contains<Tp, Rest...>
>::type {};

template < typename Tp >
struct contains<Tp> : std::false_type {};

Существует только одна внутренняя операция для шаблонов с переменными параметрами - это специальная форма оператора sizeof, который вычисляет длину списка параметров, например:

template < typename... Types >
struct typelist_len
{
   const static size_t value = sizeof...(Types);
};

Откуда вы взяли "у него серьезные накладные расходы во время компиляции" с boost mpl? Я надеюсь, что вы не просто делаете предположения здесь. Boost mpl использует такие методы, как создание ленивых шаблонов, чтобы попытаться сократить время компиляции вместо взрыва, как это делает наивное шаблонное метапрограммирование.

К счастью, стандарт C++ развился. С C++1z или C++17 вы, наконец, можете легко перебирать пакеты параметров. Таким образом, код для ответа (почти) так же прост, как предложено в вопросе:

template<typename ... Args, typename What>
struct is_prsent {
    static constexpr bool value {(std::is_same_v<What, Args> || ...)}; };

Странный вид (std::is_same_v<What, Args> || ...) расширяется компилятором внутри (std::is_same_v<What, Args[0]> || std::is_same_v<What, Args[1]> || ...), который именно то, что вы хотите. Это даже правильно дает false даже с пустым Args пакет параметров.

Можно даже выполнить всю встроенную проверку в функции или методе - вспомогательные структуры больше не требуются:

template<typename T, typename ... List>
void foo(T t, List ... lst)
{
    if constexpr((std::is_same_v<T, List> || ...)) {
        std::cout << "T is in List" << std::endl;
    } else {
        std::cout << "T is not in List" << std::endl;
    }
}

Примечание: это было взято из другого вопроса, который был отмечен как дубликат этого вопроса. Поскольку это "канонический" вопрос для этой темы, я добавил эту важную информацию здесь.

Если вы хотите избежать ручной рекурсии типа, std::common_type мне кажется, что это единственная утилита в STL, которая является шаблоном с вариациями и, следовательно, единственной, которая потенциально может инкапсулировать рекурсию.


Решение 1

std::common_type находит наименее производный тип в наборе типов. Если мы отождествляем числа с типами, особенно большие числа с менее производными типами, он находит наибольшее число в наборе. Затем мы должны отобразить равенство с типом ключа на уровень деривации.

using namespace std;

struct base_one { enum { value = 1 }; };
struct derived_zero : base_one { enum { value = 0 }; };

template< typename A, typename B >
struct type_equal {
 typedef derived_zero type;
};

template< typename A >
struct type_equal< A, A > {
 typedef base_one type;
};

template< typename Key, typename ... Types >
struct pack_any {
 enum { value =
     common_type< typename type_equal< Key, Types >::type ... >::type::value };
};


Решение 2

Мы можем взломать common_type немного больше. Стандарт говорит

Программа может специализировать эту черту, если хотя бы один параметр шаблона в специализации является пользовательским типом.

и точно описывает, что внутри него: случай рекурсивной частичной специализации, случай, в котором применяется двоичный оператор, и случай терминала. По сути, это общий fold функция, и вы можете добавить любую бинарную операцию, пожалуйста. Здесь я использовал дополнение, потому что оно более информативно, чем OR. Обратите внимание, что is_same возвращает integral_constant,

template< typename Addend >
struct type_sum { // need to define a dummy type to turn common_type into a sum
    typedef Addend type;
};

namespace std { // allowed to specialize this particular template
template< typename LHS, typename RHS >
struct common_type< type_sum< LHS >, type_sum< RHS > > {
    typedef type_sum< integral_constant< int,
     LHS::type::value + RHS::type::value > > type; // <= addition here
};
}

template< typename Key, typename ... Types >
struct pack_count : integral_constant< int,
 common_type< type_sum< is_same< Key, Types > > ... >::type::type::value > {};

Начиная с C++17, вы можете наследовать отс обычным расширением упаковкиstd::is_same<What, Args>...который выполняет логическоеORмежду всемиstd::is_sameс. Ваша типовая черта будет иметьstatic constexpr bool valueпеременная-член, содержащая результат. Это отличается от выражения сгиба над||поскольку это сокращает создание остальных частейis_same<>::values, если совпадение найдено. Вы можете увидеть, как это работает в реализации C++11/14 ниже.

      #include <type_traits>

template<class What, class... Args>
struct is_present : std::disjunction<std::is_same<What, Args>...> {};

Используя C++11 или C++14, вы можете определить свои собственныеdisjunctionс использованиемstd::conditionalи используйте эту черту типа вместоstd::disjunctionпри определенииis_present:

      template<class...> struct disjunction : std::false_type {};
template<class T> struct disjunction<T> : T {};
template<class T, class... Ts>
struct disjunction<T, Ts...> :
    std::conditional<bool(T::value),
                     T, // no `is_same<>::value` instantiations for the Ts...
                     disjunction<Ts...>>::type {};

Начиная с C++14, вы также можете создать шаблон вспомогательной переменной:

      template<class... Ts>
constexpr bool is_present_v = is_present<Ts...>::value;
      int main() {
    std::cout << is_present_v<int, double, int> << '\n'; // prints 1
}
Другие вопросы по тегам