Как удалить n-й элемент из кортежа? - PullRequest
3 голосов
/ 19 февраля 2020

Я пытаюсь написать функцию, которая создает новую std::tuple из существующей, пропуская элемент по заданному индексу. Например:

У меня есть кортеж t, определенный следующим образом:

constexpr auto t = std::tuple(1, 2, 3, 4);

И я хочу скопировать его в другой кортеж. Однако я хочу пропустить элемент nth . Предположим, что в этом случае элемент nth , который я хочу пропустить, равен 3 (это означает, что я хочу пропустить элемент с индексом 2). Это приведет к новому кортежу, определенному как:

std::tuple(1, 2, 4);

Это самое близкое, что я получил до сих пор:

template<std::size_t N, typename T, std::size_t ... is>
constexpr auto fun(T&& tp, std::index_sequence<is...>&& i) noexcept {
    return std::tuple((is != N ? std::get<is>(tp) : 0) ...);
}

template<std::size_t N, std::size_t... elems>
constexpr auto fun2() noexcept {
    constexpr auto t = std::tuple(elems...);
    return fun<N>(std::forward_as_tuple(elems...), std::make_index_sequence<sizeof...(elems)>());
}

Однако вместо удаления nth элемент, я устанавливаю его на 0.

В идеале, я бы изменил аргумент возврата в функции fun(), чтобы создать новый кортеж, используя несколько временных кортежей:

return std::tuple_cat((is != N ? std::tuple(std::get<is>(tp)) : std::tuple()) ...);

Однако, проблема заключается в том, что у троичного оператора должны быть совпадающие типы с обеих сторон.

Еще один подход, который я попробовал, был основан на рекурсии:

template<std::size_t N, std::size_t head, std::size_t... tail>
constexpr auto fun3() noexcept {
    if constexpr(!sizeof...(tail))
        return std::tuple(head);

    if constexpr(sizeof...(tail) - 1 == N)
        return std::tuple_cat(fun3<N, tail...>());

    if constexpr(sizeof...(tail) - 1 != N)
        return std::tuple_cat(std::tuple(head), fun3<N, tail...>());
}

Однако это было еще более неудачным. В этом случае, если N равно 0, элемент nth (который также является первым элементом здесь) будет по-прежнему использоваться в новом кортеже. Кроме того, это даже не скомпилируется, потому что есть проблема со вторым оператором:

if constexpr(sizeof...(tail) - 1 == N)

Что мне здесь не хватает? Как я могу скопировать кортеж и пропустить один из его элементов во время копирования?

Я использую C ++ 17, и мне нужно, чтобы функция была оценена во время компиляции.

Ответы [ 3 ]

3 голосов
/ 19 февраля 2020

Как насчет

return std::tuple_cat( foo<is, N>::func(std::get<is>(tp)) ...);

, где foo - это структура со специализацией следующим образом?

template <std::size_t, std::size_t>
struct foo
 {
   template <typename T>
   static auto func (T const & t)
    { return std::make_tuple(t); } 
 }

template <std::size_t N>
struct foo<N, N>
 {
   template <typename T>
   static std::tuple<> func (T const &)
    { return {}; } 
 }

(осторожно: код не проверен).

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

2 голосов
/ 19 февраля 2020

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

template<std::size_t nth, std::size_t... Head, std::size_t... Tail, typename... Types>
constexpr auto remove_nth_element_impl(std::index_sequence<Head...>, std::index_sequence<Tail...>, std::tuple<Types...> const& tup) {
    return std::tuple{
        std::get<Head>(tup)...,
        // We +1 to refer one element after the one removed 
        std::get<Tail + nth + 1>(tup)...
    };
}

template<std::size_t nth, typename... Types>
constexpr auto remove_nth_element(std::tuple<Types...> const& tup) {
    return remove_nth_element_impl<nth>(
        std::make_index_sequence<nth>(), // We -1 to drop one element 
        std::make_index_sequence<sizeof...(Types) - nth - 1>(),
        tup
    );
}

Вот тест для этой функции:

int main() {
    constexpr auto tup = std::tuple{1, 1.2, 'c'};
    constexpr auto tup2 = remove_nth_element<0>(tup);
    constexpr auto tup3 = remove_nth_element<2>(tup);
    static_assert(std::is_same_v<decltype(tup2), const std::tuple<double, char>>);
    static_assert(std::is_same_v<decltype(tup3), const std::tuple<int, double>>);
    return 0;
}

Живой пример

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

0 голосов
/ 19 февраля 2020

Через несколько минут после публикации вопроса я нашел обходной путь. Это не идеально, но эй:

template<std::size_t N, typename T, std::size_t ... is>
constexpr auto fun(T&& tp, std::index_sequence<is...>&& i) noexcept {
    return std::tuple((is < N ? std::get<is>(tp) : std::get<is+1>(tp)) ...);
}

template<std::size_t N, std::size_t... elems>
constexpr auto fun2() noexcept {
    constexpr auto t = std::tuple(elems...);
    return fun<N>(std::forward_as_tuple(elems...), std::make_index_sequence<sizeof... (elems) - 1>());
}

Таким образом, мы копируем все элементы до элемента nth , и когда мы достигаем элемента nth, мы увеличиваем каждый следующий индекс на 1. Мы не будем go вне диапазона, так как мы передаем index_sequence, который на 1 элемент меньше передаваемого кортежа.

Я надеюсь, что этот ответ поможет кому-то .

...