Что-то, возможно, немного более прямолинейное, хотя и более подробное: частичная специализация шаблона класса + if constexpr
:
Основной подход заключается в специализации следующего базового класса:
template<class... T>
struct flatten
{};
ДляРассмотрим три случая:
- Голое значение
- A
tuple
одной вещи - A
tuple
более чем одной вещи
Случай № 1, базовый вариант, довольно прост, просто верните то, что мы получили:
//base case: something that isn't another tuple
template<class T>
struct flatten<T>
{
template<class U>
constexpr decltype(auto) operator()(U&& _value){
return std::forward<U>(_value);
}
};
Случай № 2 также довольно прост, просто повторяйте сам, пока мы не достигнемСлучай № 1
// recursive case 1 : plain old tuple of one item
template<class T>
struct flatten<std::tuple<T>>
{
template<class U>
constexpr decltype(auto) operator()(U&& _tup){
return flatten<std::remove_cvref_t<T>>{}(std::get<0>(_tup));
}
};
Случай № 3 длинный из-за возможных подслучаев, но каждый блок довольно читабелен.Мы
- Выровняем первый элемент (возможно, повторения)
- Выровняем остальные элементы (возможные повторения)
И затем у нас есть четыре случая длярассмотрим:
- У нас есть два кортежа (например,
tuple<int, int>, tuple<int, int>
) - У нас есть кортеж и значение (например,
tuple<int, int>, int
) - У нас естьзначение и кортеж (например,
int, tuple<int, int>
) - У нас есть два значения (например,
int, int
)
Нам просто нужна одна вспомогательная функция, которая позволяет нам удалятьОтключите кортеж и верните оставшуюся часть.
// helper for getting tuple elements except the first one
template<template<class...> class Tup, class... T, size_t... indices>
constexpr auto get_rest_of_tuple(const Tup<T...>& _tup, std::index_sequence<indices...>){
return std::make_tuple(std::get<indices + 1>(_tup)...);
}
и некоторые вспомогательные черты:
// some type traits to use for if constexpr
template<class T>
struct is_tuple : std::false_type{};
template<class... T>
struct is_tuple<std::tuple<T...>> : std::true_type{};
template<class T>
constexpr bool is_tuple_v = is_tuple<T>::value;
Наконец, impl:
// recursive case 2: tuple of more than one item
template<class First, class Second, class... Rest>
struct flatten<std::tuple<First, Second, Rest...>>
{
template<class Tup>
constexpr decltype(auto) operator()(Tup&& _tup){
auto flattened_first = flatten<std::remove_cvref_t<First>>{}(std::get<0>(_tup));
auto restTuple = get_rest_of_tuple(_tup, std::make_index_sequence<sizeof...(Rest)+1>{});
auto flattened_rest = flatten<std::remove_cvref_t<decltype(restTuple)>>{}(restTuple);
// both are tuples
if constexpr(is_tuple_v<decltype(flattened_first)> && is_tuple_v<decltype(flattened_rest)>)
{
return std::tuple_cat(flattened_first, flattened_rest);
}
// only second is tuple
if constexpr(!is_tuple_v<decltype(flattened_first)> && is_tuple_v<decltype(flattened_rest)>)
{
return std::tuple_cat(std::make_tuple(flattened_first), flattened_rest);
}
//only first is tuple
if constexpr(is_tuple_v<decltype(flattened_first)> && !is_tuple_v<decltype(flattened_rest)>)
{
return std::tuple_cat(flattened_first, std::make_tuple(flattened_rest));
}
// neither are tuples
if constexpr(!is_tuple_v<decltype(flattened_first)> && !is_tuple_v<decltype(flattened_rest)>)
{
return std::tuple_cat(std::make_tuple(flattened_first), std::make_tuple(flattened_rest));
}
}
};
} // namespace detail
Наконец, мыиспользуйте trampolining, чтобы скрыть все эти детали от конечного пользователя, поместив их в пространство имен details
и предоставив следующую функцию для вызова в них:
template<class T>
constexpr decltype(auto) flatten(T&& _value){
return detail::flatten<std::remove_cvref_t<T>>{}(std::forward<T>(_value));
}
(включает некоторые дополнительные тесты на корректность)
Хотя приведенный выше пример № 3 довольно прост, он одновременно многословен и немного неэффективен(компилятор оценивает каждое из этих if constexpr
выражений, когда он должен оценивать только одно, но я не хотел проводить нити вдоль else
ветвей из-за вложенности).
Мы можем довольно сильно упростить Case #3 путем переключения на две вспомогательные функции, которые определяют, является ли аргумент кортежом not, и возвращают правильную вещь:
template<class U, std::enable_if_t<!is_tuple_v<U>, int> = 0>
constexpr decltype(auto) flatten_help(U&& _val){
return std::make_tuple(_val);
}
template<class... T>
constexpr decltype(auto) flatten_help(const std::tuple<T...>& _tup){
return _tup;
}
// recursive case 2: tuple of more than one item
template<class First, class Second, class... Rest>
struct flatten<std::tuple<First, Second, Rest...>>
{
template<class Tup>
constexpr decltype(auto) operator()(Tup&& _tup){
auto flattened_first = flatten<std::remove_cvref_t<First>>{}(std::get<0>(_tup));
auto restTuple = get_rest_of_tuple(_tup, std::make_index_sequence<sizeof...(Rest)+1>{});
auto flattened_rest = flatten<std::remove_cvref_t<decltype(restTuple)>>{}(restTuple);
return std::tuple_cat(flatten_help(flattened_first), flatten_help(flattened_rest));
}
};