Вариационные шаблоны: создание кортежа пар смежных элементов
моя цель-сделать что-то, что, например,
pairs<1,2,3,4>()
возвращает значение типа
std::tuple<some_other_type<1,2>, some_other_type<2,3>, some_other_type<3,4>>
мне интересно, возможно ли это даже с метапрограммированием шаблонов C++ и как это можно сделать. Для фактического создания значения кажется, что я могу использовать tuple_cat для рекурсивного объединения с выходом, но мне трудно выразить возвращаемый тип, так как он сам по себе вариативен и эффективно является функцией количества шаблонов параметры. Усложняя ситуацию, если бы я пошел по маршруту tuple_cat, похоже, мне также пришлось бы перегрузить функцию, чтобы взять Кортеж для конкатенации, и конкатенация произойдет во время выполнения, а не во время компиляции. Я что, гоняюсь за несбыточным?
3 ответов
вот способ сделать это. Учитывая ваш шаблон класса some_other_type
:
template<int I, int J>
struct some_other_type { };
и учитывая некоторые механизмы, скрытые в detail
пространство имен:
namespace detail
{
template<int... Is>
struct pairs { };
template<int I, int J>
struct pairs<I, J>
{
using type = std::tuple<some_other_type<I, J>>;
};
template<int I, int J, int... Is>
struct pairs<I, J, Is...>
{
using type = decltype(std::tuple_cat(
std::tuple<some_other_type<I, J>>(),
typename pairs<J, Is...>::type()));
};
}
вы можете предоставить простую функцию, которая создает экземпляр шаблона вспомогательного класса:
template<int... Is>
typename detail::pairs<Is...>::type pairs()
{
return typename detail::pairs<Is...>::type();
}
и вот как вы бы использовали его (и тестовый пример):
#include <type_traits>
int main()
{
auto p = pairs<1, 2, 3, 4>();
// Won't fire!
static_assert(
std::is_same<
decltype(p),
std::tuple<
some_other_type<1,2>,
some_other_type<2,3>,
some_other_type<3,4>>
>::value,
"Error!");
}
наконец, вот это видео.
благоустройство: (зачем писать <1, 2, 3, 4>
когда можно написать <1, 5>
)?
также можно расширить вышеуказанное решение, чтобы не требовалось вручную записывать каждое число между минимумом и максимумом в качестве аргумента шаблона pairs()
. Учитывая дополнительный механизм ниже, снова скрытый в detail
пространство имен:
namespace detail
{
template <int... Is>
struct index_list { };
template <int MIN, int N, int... Is>
struct range_builder;
template <int MIN, int... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
template <int MIN, int N, int... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{ };
// Meta-function that returns a [MIN, MAX) index range
template<int MIN, int MAX>
using index_range = typename range_builder<MIN, MAX>::type;
template<int... Is>
auto pairs_range(index_list<Is...>) -> decltype(::pairs<Is...>())
{
return ::pairs<Is...>();
}
}
можно определить вспомогательную функцию pairs_range()
который принимает 2 аргумента шаблона, определяющих диапазон [begin, end)
- где end
не входит, в стиле стандартной библиотеки:
template<int I, int J>
auto pairs_range() -> decltype(pairs_range(detail::index_range<I, J>()))
{
return pairs_range(detail::index_range<I, J>());
}
и вот как его можно было бы использовать (включая тестовый случай):
int main()
{
// Won't fire!
static_assert(
std::is_same<
decltype(pairs_range<1, 5>()),
decltype(pairs<1, 2, 3, 4>())
>::value,
"Error!");
}
и еще раз, вот это видео.
вот моя версия его (видео здесь), 100% время компиляции, возвращая новый список параметров как тип (не возврат функции):
во-первых, давайте определим наши структуры результатов:
template<int a, int b>
struct tpair
{
};
template<typename... p>
struct final_
{
};
ключевым моментом является конкат параметры пакетов. Вот структура, которая будет выполнять эту работу:
template<typename a, typename b>
struct concat
{
};
template<typename a, typename... b>
struct concat<a, final<b...>>
{
typedef final_<a,b...> type;
};
Теперь структура используется для "pairize" ваш список. Обычно он будет терпеть неудачу с нечетными числами значений:
template<int a, int b, int... values>
struct pairize
{
// Choose one of the following versions:
// First version: only non-overlapping pairs : (1,2) (3,4) ...
typedef typename concat<tpair<a,b>, typename pairize<values...>::type>::type type;
// Second version: overlapping pairs : (1,2) (2,3) (3,4)...
typedef typename concat<tpair<a,b>, typename pairize<b,values...>::type>::type type;
};
template<int a, int b>
struct pairize<a,b>
{
typedef final_<tpair<a,b>> type;
};
в живом примере есть также является кодом, выводящим имя всех типов в пакете параметров на консоль, с demangling, в качестве теста (было смешнее использовать, чем неполный трюк типа).
а теперь, давайте попробуем с indices
и без рекурсии (за исключением, конечно, для индексов):
#include <tuple>
template< std::size_t... Ns >
struct indices
{
typedef indices< Ns..., sizeof...( Ns ) > next;
};
template< std::size_t N >
struct make_indices
{
typedef typename make_indices< N - 1 >::type::next type;
};
template<>
struct make_indices< 0 >
{
typedef indices<> type;
};
template< std::size_t, std::size_t >
struct sometype {};
template< typename, typename, typename >
struct make_pairs;
template< std::size_t... Ns, std::size_t... Ms, std::size_t... Is >
struct make_pairs< indices< Ns... >, indices< Ms... >, indices< Is... > >
{
using type = decltype( std::tuple_cat(
std::declval< typename std::conditional< Is % 2 == 1,
std::tuple< sometype< Ns, Ms > >,
std::tuple<> >::type >()...
));
};
template< std::size_t... Ns >
using pairs = typename make_pairs< indices< 0, Ns... >, indices< Ns..., 0 >,
typename make_indices< sizeof...( Ns ) + 1 >::type >::type;
int main()
{
static_assert( std::is_same< pairs<1,2,4,3,5,9>,
std::tuple< sometype<1,2>, sometype<4,3>, sometype<5,9> > >::value, "Oops" );
}
(хорошо, я немного обманул:std::tuple_cat
может быть рекурсивной ;)
обновление: хорошо, я должен был прочитать вопрос более внимательно. Вот версия, которая дает желаемый результат (indices
/ make_indices
как выше):
template< std::size_t, std::size_t >
struct sometype {};
template< typename, typename, typename >
struct make_pairs;
template< std::size_t... Ns, std::size_t... Ms, std::size_t... Is >
struct make_pairs< indices< Ns... >, indices< Ms... >, indices< Is... > >
{
using type = decltype( std::tuple_cat(
std::declval< typename std::conditional< Is != 0 && Is != sizeof...( Is ) - 1,
std::tuple< sometype< Ns, Ms > >,
std::tuple<> >::type >()...
));
};
template< std::size_t... Ns >
using pairs = typename make_pairs< indices< 0, Ns... >, indices< Ns..., 0 >,
typename make_indices< sizeof...( Ns ) + 1 >::type >::type;
int main()
{
static_assert( std::is_same< pairs<1,2,3,4>,
std::tuple< sometype<1,2>, sometype<2,3>, sometype<3,4> > >::value, "Oops" );
}