Как «развернуть» std :: array во время компиляции в пакет параметров? - PullRequest
3 голосов
/ 27 февраля 2020

Я бы хотел использовать частичную специализацию шаблонов, чтобы «разбить» массив (который создается во время компиляции) на пакет параметров, состоящий из его значений (для взаимодействия с другими структурами, которые я определил в своем коде). Следующее (моя первая попытка) не компилирует

#include <array>

template <typename T, auto k> struct K;
template <typename T, std::size_t... A> struct K<T, std::array<std::size_t, sizeof...(A)>{A...}> {};

, потому что аргумент шаблона std::array<long unsigned int, sizeof... (A)>{A ...} не должен включать параметры шаблона . Насколько я понял, невозможно обеспечить параметры не-типа в частичной специализации шаблона, если они нетривиально зависят от параметров шаблона. Поэтому я попытался обойти эту проблему, указав значение в виде:

#include <array>

template <auto f> struct any_type;

template <typename T, typename array_wrapper> struct FromArr;
template <typename T, std::size_t... A>
struct FromArr<T, any_type<std::array<std::size_t, sizeof...(A)>{A...}>> {};

int main() {
  FromArr<int, any_type<std::array<std::size_t, 2>{1, 2}>> d;
  (void) d;
}

Однако здесь, частичная специализация шаблона завершается неудачно, когда я пытаюсь его использовать; приведенное выше определение не соответствует тому, как я его использую, и я не уверен, почему. Сбой со следующей ошибкой:

file.cc: In function ‘int main()’:
file.cc:10:55: error: aggregate ‘FromArr<int, Any<std::array<long unsigned int, 2>{std::__array_traits<long unsigned int, 2>::_Type{1, 2}}> > d’ has incomplete type and cannot be defined
  10  |   FromArr<int, Any<std::array<std::size_t, 2>{1, 2}>> d;

Можно ли обойти это / использовать другой подход для интерфейса массива как пакета параметров?

Использованный компилятор

Я использую g++-10.0 (GCC) 10.0.1 20200124 (experimental) и компилирую с помощью g++ -std=c++2a file.cc, c ++ 2a требуется, потому что я использую нетипичные параметры шаблона.

Редактировать:

Описание того, как массив должен быть обработанным

В моем реальном коде у меня есть структура, которая зависит, среди прочего, от пакета параметров (1). Было бы неплохо, если бы я мог использовать массив (2) (который я получил в другой части моего кода в качестве аргумента шаблона нетипичного типа) для взаимодействия с этой структурой, как показано в коде ниже.

template <int... s> struct toBeUsed;                               // (1)
template <std::size_t s, std::array<int, s> arr> struct Consumer { // (2)
    toBeUsed<arr> instance; // This is what I would like to do
}

Моя попытка написать вспомогательную структуру, как указано выше FromStruct, которую я могу создать с помощью array, в котором у меня есть typedef FromStruct::type, который предоставляет toBeUsed с правильными аргументами , аналогично этому примеру , который делает то, что я хочу сделать здесь с типами, из которых состоит std :: tuple.

Ссылка на пример

здесь Я привожу упрощенный пример использования (2-й блок кода).

Ответы [ 2 ]

1 голос
/ 27 февраля 2020

Вдохновленный ответом @dfri, я преобразовал ее / ее решение в версию, в которой могут отсутствовать функции, но вместо этого использует только одну структуру, используя частичную специализацию шаблона для std::integer_sequence, что также может быть интересно другим:

template <auto arr, template <typename X, X...> typename Consumer,
          typename IS = decltype(std::make_index_sequence<arr.size()>())> struct Generator;

template <auto arr, template <typename X, X...> typename Consumer, std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...>> {
  using type = Consumer<typename decltype(arr)::value_type, arr[I]...>;
};

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

#include <array>

/// Structure which wants to consume the array via a parameter pack.
template <typename StructuralType, StructuralType... s> struct ConsumerStruct {
  constexpr auto operator()() const { return std::array{s...}; }
};

/// Solution
template <auto arr, template <typename X, X...> typename Consumer,
          typename IS = decltype(std::make_index_sequence<arr.size()>())> struct Generator;

template <auto arr, template <typename X, X...> typename Consumer, std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...>> {
  using type = Consumer<typename decltype(arr)::value_type, arr[I]...>;
};

/// Helper typename
template <auto arr, template <typename T, T...> typename Consumer>
using Generator_t = typename Generator<arr, Consumer>::type;

// Usage
int main() {
  constexpr auto tup = std::array<int, 3>{{1, 5, 42}};
  constexpr Generator_t<tup, ConsumerStruct> tt;
  static_assert(tt() == tup);
  return 0;
}
1 голос
/ 27 февраля 2020

Подход C ++ 20

См. собственный ответ OP или, возможно, поучительный, но более подробный (и менее полезный) подход, редакция 2 этого ответа .

Подход C ++ 17

(Этот ответ изначально содержал подход, использующий незначительную особенность C ++ 20 (что лямбда без каких-либо перехватов может быть построена по умолчанию) , но вдохновленный первоначальным ответом, OP предоставил гораздо более аккуратный подход C ++ 20, используя тот факт, что constexpr std::array подпадает под тип литерального класса, который может быть передан как параметр шаблона нетипичного типа в C ++ 20 (с учетом ограничений на ::value_type) в сочетании с частичной специализацией последовательности индексов, используемой для распаковки массива в пакет параметров.В этом оригинальном ответе использовалась методика переноса std::array в constexpr лямбда (> = C ++ 17), который действовал как constexpr (специфицированный c) std::array создатель вместо фактического constexpr std::array. Подробнее об этом подходе: см. редакцию 2 этого ответ )

Следуя аккуратному подходу OP, ниже приведена его адаптация для C ++ 17 с использованием не шаблонного параметра шаблона lvalue для предоставления, во время компиляции, ссылки на массив в массив для структурирования цели.

#include <array>
#include <cstdlib>
#include <tuple>
#include <type_traits>
#include <utility>

// Parameter pack structure (concrete target for generator below).
template <typename StructuralType, StructuralType... s>
struct ConsumerStruct
{
    // Use tuple equality testing for testing correctness.
    constexpr auto operator()() const { return std::tuple{s...}; }
};

// Generator: FROM std::array TO Consumer.
template <const auto& arr,
          template <typename T, T...> typename Consumer,
          typename Indices = std::make_index_sequence<arr.size()> >
struct Generator;

template <const auto& arr,
          template <typename T, T...> typename Consumer,
          std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...> >
{
    using type =
        Consumer<typename std::remove_cv<typename std::remove_reference<
                     decltype(arr)>::type>::type::value_type,
                 arr[I]...>;
};

// Helper.
template <const auto& arr, template <typename T, T...> typename Consumer>
using Generator_t = typename Generator<arr, Consumer>::type;

// Example usage.
int main()
{
    // As we want to use the address of the constexpr std::array at compile
    // time, it needs to have static storage duration.
    static constexpr std::array<int, 3> arr{{1, 5, 42}};
    constexpr Generator_t<arr, ConsumerStruct> cs;
    static_assert(cs() == std::tuple{1, 5, 42});
    return 0;
}

Обратите внимание, что этот подход накладывает ограничение на экземпляр std::array в том смысле, что он должен иметь длительность хранения c. Если кто-то хочет этого избежать, в качестве альтернативы можно использовать constexpr лямбду, которая генерирует массив.

...