Вариантный шаблон constexpr и распаковка std :: array - PullRequest
0 голосов
/ 22 мая 2018

Я хотел бы написать шаблонную функцию constexpr, которая переставляет элементы массива, переданного в качестве параметра.Итак, я придумал что-то вроде этого:

template <typename T, std::size_t N, typename... Ts>
constexpr std::array<T, N> permute(const std::array<T, N>& arr, const std::array<int, N>& permutation, Ts&&... processed)
{
    return (sizeof...(Ts) == N) ?
        std::array<T, N>{ std::forward<Ts>(processed)... } :
        permute(arr, permutation, std::forward<Ts>(processed)..., arr[permutation[sizeof...(Ts)]]);
}

Пример использования:

constexpr std::array<int, 3> arr{ 1, 2, 3 };
constexpr std::array<int, 3> permutation{ 2, 1, 0 };
constexpr auto result = permute(arr, permutation); //result should contain { 3, 2, 1 }

Проблема в том, что приведенный выше код не компилируется.По какой-то причине g ++ 6.4 пытается создать экземпляр шаблона перестановки с 4 и более параметрами, скрытыми в 'обработанном' пакете параметров шаблона.Можете ли вы помочь мне исправить мой код и заставить его скомпилироваться?

Полный код

1 Ответ

0 голосов
/ 22 мая 2018

Я представлю «быстрое исправление», которое покажет причину проблемы, а затем покажу, как решить проблему в C ++ 11.После этого я покажу, как использовать более новые функции (начиная с C ++ 14), чтобы получить более простую реализацию.


Диагностика

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

В более новых версиях C ++ мы можем заставить его работать, заменив ? с if constexpr:

#include <array>
#include <cstddef>
#include <utility>

template <typename T, std::size_t N, typename... Ts>
constexpr std::array<T, N> permute(const std::array<T, N>& arr,
                                   const std::array<int, N>& permutation,
                                   Ts&&... processed)
{
    if constexpr (sizeof...(Ts) == N)
        return std::array<T, N>{ std::forward<Ts>(processed)... };
    else
        return permute(arr, permutation, std::forward<Ts>(processed)...,
                       arr[permutation[sizeof...(Ts)]]);
}

int main()
{
    constexpr std::array<int, 3> arr{ 1, 2, 3 };
    constexpr std::array<int, 3> permutation{ 2, 1, 0 };
    constexpr auto result = permute(arr, permutation);

    return result != std::array<int, 3>{ 3, 2, 1 };
}

(Для этих более новых версий C ++ это все еще можно упростить с помощью std::index_sequence, как я покажу позже).


C ++ 11 код

C ++ 11 не имеет if constexpr, поэтому нам нужно вместо этого вернуться к SFINAE:

#include <array>
#include <cstddef>
#include <utility>

template <typename T, std::size_t N, typename... Ts>
constexpr typename std::enable_if<sizeof...(Ts) == N, std::array<T, N> >::type
permute(const std::array<T, N>&, const std::array<int, N>&,
        Ts&&... processed)
{
    return std::array<T, N>{ std::forward<Ts>(processed)... };
}

template <typename T, std::size_t N, typename... Ts>
constexpr typename std::enable_if<sizeof...(Ts) != N, std::array<T, N> >::type
permute(const std::array<T, N>& arr, const std::array<int, N>& permutation,
        Ts&&... processed)
{
    return permute(arr, permutation, std::forward<Ts>(processed)...,
                   arr[permutation[sizeof...(Ts)]]);
}

Здесь мы предоставляем полностьюотдельные функции для sizeof...(Ts) == N и sizeof...(Ts) != N, и используйте std::enable_if для выбора между ними.


C ++ 14 и далее

Если мы можем использовать C ++14 или позже, мы получаем std::index_sequence, что значительно упрощает работу со всеми элементами массива или кортежа.Для этого по-прежнему требуются две функции, но на этот раз одна из них вызывает другую, и логика немного проще для понимания:

#include <array>
#include <cstddef>
#include <utility>

template<typename T, std::size_t N, std::size_t... I>
constexpr std::array<T, N>
permute_impl(const std::array<T, N>& a, const std::array<int, N>& p,
             std::index_sequence<I...>)
{
    return { a[p[I]]... };
}


template<typename T, std::size_t N, typename I = std::make_index_sequence<N>>
constexpr std::array<T, N>
permute(const std::array<T, N>& a, const std::array<int, N>& p)
{
    return permute_impl(a, p, I{});
}

Возможно, вам даже стоит реализовать собственную index_sequence, если вам это нужноболее одного раза, и вы вынуждены использовать только C ++ 11.

...