Подтвердить что ты не робот

С++ 11 constexpr сгладить список std:: array в массив

Я начинаю с С++ 11, метапрограммирование constexpr и template кажется хорошим способом сберечь ограниченный баран на крошечном микроконтроллере.

Есть ли способ написать шаблон, чтобы сгладить список массива constexpr, что Мне нужен способ:

constexpr std::array<int, 3> a1 = {1,2,3};
constexpr std::array<int, 2> a2 = {4,5};
constexpr auto a3 = make_flattened_array (a1,a2);

Я использую gcc 4.8.4 (arm-none-eabi) и могу скомпилировать с помощью std = С++ 11 или С++ 1y, если это необходимо.

4b9b3361

Ответ 1

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

Вы можете достичь своей цели тремя концепциями С++ 11 +:

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

template<unsigned N1, unsigned N2>
constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){
  // TODO
}

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

Теперь для интересной части: std:: array имеет (с С++ 1y) a constexpr перегрузка для оператора [], это означает, что вы можете написать что-то вроде

template<unsigned N1, unsigned N2>
constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){
  return std::array<int,N1+N2>{a1[0],a1[1],a1[2],a2[0],a2[1]};
}

(обратите внимание на aggregate-initialization для инициализации объекта из целого ряда значений)

Очевидно, что жесткое кодирование вручную всех обращений к значениям этих двух массивов не лучше, чем просто объявление самого конкатенированного массива. Концепция, которая сохранит этот день, следующая: Пакеты параметров. Пакет параметров шаблона является параметром шаблона, который принимает 0 или более аргументов шаблона. Шаблон с хотя бы одним пакетом параметров называется вариационным шаблоном.

Приятная вещь - это возможность расширения пакета параметров в определенные места, например:

#include <iostream>
#include <array>

template<unsigned... Num>
std::array<int, 5> function(const std::array<int,5>& source) {
    return std::array<int,5>{source[Num]...};
}


int main() {
    std::array<int,5> source{7,8,9,10,11};
    std::array<int,5> res = function<0,1,2,3,4>(source);

    for(int i=0; i<res.size(); ++i)
        std::cout << res[i] << " "; // 7 8 9 10 11

    return 0;
}

Итак, единственное, что нам нужно прямо сейчас - это возможность компиляции-времени генерировать "индексный ряд", например

std::array<int,5> res = function<0,1,2,3,4>(source);
                                 ^ ^ ^ ^ ^

В этот момент мы снова можем воспользоваться пакетами параметров в сочетании с механизмом наследования: идея состоит в том, чтобы иметь глубоко вложенную иерархию классов derived : base : other_base : another_base : ..., которая "накапливает" индексы в пакете параметров и завершает "рекурсия", когда индекс достигает 0. Если вы не поняли предыдущее предложение, не волнуйтесь и посмотрите на следующий пример:

std::array<int, 3> a1{42,26,77};

// goal: having "Is" = {0,1,2} i.e. a1 valid indices
template<unsigned... Is> struct seq;

мы можем сгенерировать последовательность индексов следующим образом:

template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, Is...>{}; // each time decrement the index and go on
template<unsigned... Is>
struct gen_seq<0 /*stops the recursion*/, Is...> : /* generate the sequence */seq<Is...>{};

std::array<int, 3> a1{42,26,77};
gen_seq<3>{};

В любом случае чего-то не хватает: приведенный выше код начинается с gen_seq < 3, (nothing) > и создает экземпляр указанного шаблона, который будет создавать экземпляр gen_seq < 2, (nothing) > в качестве базового класса, который будет создавать экземпляр gen_seq < 1, (nothing) > в качестве своего базового класса, который будет создавать экземпляр gen_seq < 0, (nothing) > в качестве базового класса, который будет создавать последовательность seq < (nothing) > в качестве окончательной последовательности.

Последовательность "(ничего)", что-то не так.

Чтобы "скопировать" индексы в пакет параметров, вам нужно "добавить копию" уменьшенного индекса в пакет параметров при каждой рекурсии:

template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, /*This copy goes into the parameter pack*/ N-1, Is...>{};

template<unsigned... Is>
struct gen_seq<0 /*Stops the recursion*/, Is...> : /*Generate the sequence*/seq<Is...>{};
template<unsigned... Is> struct seq{};

// Using '/' to denote (nothing)
gen_seq<3,/> : gen_seq<2, 2,/> : gen_seq<1,  1,2,/> : gen_seq<0, 0,1,2,/> : seq<0,1,2,/> .

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

Следующий код на данном этапе должен быть легко понятным:

#include <iostream>
#include <array>

template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
// Expansion pack
constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2, seq<I1...>, seq<I2...>){
  return { a1[I1]..., a2[I2]... };
}

template<unsigned N1, unsigned N2>
// Initializer for the recursion
constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){
  return concat(a1, a2, gen_seq<N1>{}, gen_seq<N2>{});
}

int main() {
    constexpr std::array<int, 3> a1 = {1,2,3};
    constexpr std::array<int, 2> a2 = {4,5};

    constexpr std::array<int,5> res = concat(a1,a2);
    for(int i=0; i<res.size(); ++i)
        std::cout << res[i] << " "; // 1 2 3 4 5

    return 0;
}

http://ideone.com/HeLLDm


Литература:

fooobar.com/questions/52917/...

http://en.cppreference.com/

http://en.wikipedia.org

Ответ 2

С С++ 1y реализация может (хотя и не требуется) разрешать std::tuple_cat работать с любыми типами типа, а не только std::tuple<T...>. В нашем случае std::array<T, N> - такой тип. Поэтому мы могли бы попытаться:

constexpr std::array<int, 3> a1 = {1, 2, 3};
constexpr std::array<int, 2> a2 = {4, 5};
constexpr auto a3 = std::tuple_cat(a1, a2);
// note:

// not possible
// constexpr auto e = a3[3]

// instead
constexpr auto e = std::get<3>(a3);

Как бы то ни было, результатом вызова std::tuple_cat является кортеж, а не массив. Затем можно превратить std::tuple<T, T,… , T> в std::array<T, N>:

template<
    typename Tuple,
    typename VTuple = std::remove_reference_t<Tuple>,
    std::size_t... Indices
>
constexpr std::array<
    std::common_type_t<std::tuple_element_t<Indices, VTuple>...>,
    sizeof...(Indices)
>
to_array(Tuple&& tuple, std::index_sequence<Indices...>)
{
    return { std::get<Indices>(std::forward<Tuple>(tuple))... };
}

template<typename Tuple, typename VTuple = std::remove_reference_t<Tuple>>
constexpr decltype(auto) to_array(Tuple&& tuple)
{
    return to_array(
        std::forward<Tuple>(tuple),
        std::make_index_sequence<std::tuple_size<VTuple>::value> {} );
}

(Как оказалось, реализация to_array преобразует любые кортежи в массив, если типы элементов набора совпадают.)

Heres живой пример для GCC 4.8, заполняя некоторые из функций С++ 1y, которые еще не поддерживаются.

Ответ 3

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

Эскиз:

#include <array>

template<class L, class R>
struct AddOp
{
    L const& l_;
    R const& r_;

    typedef typename L::value_type value_type;

    AddOp operator=(AddOp const&) = delete;

    constexpr value_type const& operator[](size_t idx) const {
        return idx < l_.size() ? l_[idx] : r_[idx - l_.size()];
    }

    constexpr std::size_t size() const {
        return l_.size() + r_.size();
    }

    // Implement the rest of std::array<> interface as needed.
};

template<class L, class R>
constexpr AddOp<L, R> make_flattened_array(L const& l, R const& r) {
    return {l, r};
}

constexpr std::array<int, 3> a1 = {1,2,3};
constexpr std::array<int, 2> a2 = {4,5};
constexpr std::array<int, 2> a3 = {6};
constexpr auto a4 = make_flattened_array(a1,a2);
constexpr auto a5 = make_flattened_array(a4,a3);

int main() {
    constexpr auto x = a5[1];
    constexpr auto y = a5[4];
    constexpr auto z = a5[5];
}

Ответ 4

Люк отправил ответ на вопрос.
Но для удовольствия, вот С++ 14 решение без шаблона метапрограммирования, просто чистый constexpr.

Есть уловка, хотя обобщенный constexpr был проголосован за стандартный основной язык более года назад, но STL все еще не обновляется...

В качестве эксперимента откройте заголовок <array> и добавьте явно отсутствующий constexpr для неконстантного оператора []

constexpr reference operator[](size_type n);

Также откройте <numeric> и включите std:: accumulate в функцию constexpr

template <class InputIterator, class T>
constexpr T accumulate(InputIterator first, InputIterator last, T init);

Теперь мы можем сделать:

#include <iostream>
#include <array>
#include <numeric>

template <typename T, size_t... sz>
constexpr auto make_flattened_array(std::array<T, sz>... ar)
{
   constexpr size_t NB_ARRAY = sizeof...(ar);

   T* datas[NB_ARRAY] = {&ar[0]...};
   constexpr size_t lengths[NB_ARRAY] = {ar.size()...};

   constexpr size_t FLATLENGTH = std::accumulate(lengths, lengths + NB_ARRAY, 0);

   std::array<T, FLATLENGTH> flat_a = {0};

   int index = 0;
   for(int i = 0; i < NB_ARRAY; i++)
   {
      for(int j = 0; j < lengths[i]; j++)
      {
         flat_a[index] = datas[i][j];
         index++;
      }
   }

   return flat_a;
}

int main()
{
  constexpr std::array<int, 3> a1 = {1,2,3};
  constexpr std::array<int, 2> a2 = {4,5};
  constexpr std::array<int, 4> a3 = {6,7,8,9};

  constexpr auto a = make_flattened_array(a1, a2, a3);

  for(int i = 0; i < a.size(); i++)
     std::cout << a[i] << std::endl;
}

(Скомпилировать и запустить на clang trunk)