LINUX.ORG.RU

Как создать массив в С++

 


0

3

Нужно создать массив с элементами у которых разный размер. Размер массива, элементов, и их порядок известен в compile-time. В коде могу представить так:

template<int N = 5>
struct A {
  std::array<int, N> = {
    // Вот эти значения должны как то создаваться автоматически
    // 1...N, N = 5
    std::array<int, 1>,
    std::array<int, 2>,
    std::array<int, 3>,
    std::array<int, 4>,
    std::array<int, 5>, 
  };
};


template<int N = 3>
struct B {
  std::array<int, N> = {
    // Вот эти значения должны как то создаваться автоматически
    std::array<int, 1>,
    std::array<int, 2>,
    std::array<int, 3>,
  };
};

Есть что то готовое, что можно использовать, и работающее в компилтайме?

-----------------------------------------------------

Первое решение:

#include <type_traits>
#include <iostream>
#include <tuple>
#include <array>

template <typename... Ts>
class tuple_array_t : public std::tuple<Ts...> {
public:
    using std::tuple<Ts...>::tuple;

    template <size_t N>
    constexpr auto&& operator[](std::integral_constant<size_t, N>) {
        return std::get<N>(*this);
    }

    [[nodiscard]] constexpr auto size() const {
        return std::integral_constant<std::size_t, std::tuple_size_v<decltype(static_cast<std::tuple<Ts...>>(*this))>>();
    }
};

template <std::size_t From, std::size_t To, class F>
constexpr void constexpr_for_range(F&& f) {
    if constexpr (From < To) {
        f(std::integral_constant<size_t, From>());
        constexpr_for_range<(std::size_t)From + 1, (std::size_t)To>(f);
    }
}

template<typename T, size_t N, typename... Ts>
constexpr auto linear_tuple_array() {
    if constexpr (N > 1) {
        return linear_tuple_array<T, N - 1, Ts..., std::array<T, N>>();
    } else {
        return tuple_array_t<Ts..., std::array<T, N>>();
    }
}

constexpr auto generate_super_array() {
    auto linear_array = linear_tuple_array<float, 10>();
    auto linear_array_size = linear_array.size();
    constexpr_for_range<0, linear_array.size()>([&] (auto i) {
        constexpr auto n = linear_array_size - i;
        constexpr_for_range<0, n>([&] (auto j) {
            linear_array[i][j] = i * 1000 + j;
        });
    });
    return linear_array;
}

int main() {
    auto array = generate_super_array();

    auto x = std::integral_constant<std::size_t, 5>();
    auto y = std::integral_constant<std::size_t, 3>();
    
    // #output: 5003
    std::cout << array[x][y] << std::endl;
}

------------------------------------------------
Второе решение

#include <cstddef>
#include <type_traits>
#include <iostream>

template <typename T, size_t N>
class ctmat;

template <typename T>
class ctmat<T, 1>
{
public:
    template <size_t i, size_t j>
    typename std::enable_if<i == 0 && j == 0, T>::type& get() { return m_v[0]; }
    template <size_t i, size_t j>
    typename std::enable_if<i == 0 && j == 0, T>::type const& get() const { return m_v[0]; }
    template <typename U>
    friend std::ostream& operator<<(std::ostream& out, const ctmat<U, 1>& m);
private:
    T m_v[1];
};

template <typename T, size_t N>
class ctmat: public ctmat<T, N-1>
{
public:
    typedef ctmat<T, N-1> base_type;
    using base_type::get;
    template <size_t i, size_t j>
    typename std::enable_if<i == N-1 && j < N, T>::type& get() { return m_v[j]; }
    template <size_t i, size_t j>
    typename std::enable_if<i == N-1 && j < N, T>::type const& get() const { return m_v[j]; }
    base_type& base() { return static_cast<base_type>(*this); }
    base_type const& base() const { return static_cast<const base_type&>(*this); }
    template <typename U, size_t M>
    friend std::ostream& operator<<(std::ostream& out, const ctmat<U, M>& m);
private:
    T m_v[N];
};

template <typename T>
std::ostream& operator<<(std::ostream& out, const ctmat<T,1>& m)
{
    out << m.m_v[0] << std::endl;
    return out;
}

template <typename T, size_t N>
std::ostream& operator<<(std::ostream& out, const ctmat<T, N>& m)
{
    out << m.base();
    for (size_t j = 0; j != N; ++j)
        out << m.m_v[j] << ' ';
    out << std::endl;
    return out;
}

int main()
{
    ctmat<int, 1> m1;
    static_assert(sizeof(m1) == sizeof(int), "Unexpected size");
    m1.get<0,0>() = 1;
    //m1.get<1,0>() = 2;
    //m1.get<0,1>() = 3;
    std::cout << m1;
    ctmat<int, 2> m2;
    static_assert(sizeof(m2) == 3*sizeof(int), "Unexpected size");
    m2.get<0,0>() = 4;
    //m2.get<0,1>() = 5;
    m2.get<1,0>() = 6;
    m2.get<1,1>() = 7;
    std::cout << m2;
    return 0;
}

★★★★★

Последнее исправление: MOPKOBKA (всего исправлений: 4)

Что значит разный размер? Может тебе и кажется, что ты нормально сформулировал, но это совсем не так. Приведи пример кода, который пользуется этим «массивом» с «разным размером».

firkax ★★★★★
()
Ответ на: комментарий от firkax
template<int N = 5>
struct A {
  std::super_array<int, N, 1..N> data = { ... };

  void zero_all() {
    for(int i = 0;i < N;i++)
      for(int j = 0;j < i+1;j++)
        data[i][j] = 0;
  }
};
MOPKOBKA ★★★★★
() автор топика

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

filosofia
()

это всё должно быть сделано на чистых C++ или можно использовать ещё какие-нибудь библиотеки? как вариант - QObject.

или не имеется ввиду разный размер самих объектов?

NAY_GIGGER
()
Последнее исправление: NAY_GIGGER (всего исправлений: 1)
Ответ на: комментарий от NAY_GIGGER

это всё должно быть сделано на чистых C++ или можно использовать ещё какие-нибудь библиотеки?

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

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

Т.е. будущий «контент» уже заранее полностью известен и надо просто его запихнуть разом?

Тогда, почему-бы не использовать Qt т.к. там есть QObject.

Ошибся с void*, хотя в теории можно и с ним разово но это уже будет геморройно придумывать.

NAY_GIGGER
()
Последнее исправление: NAY_GIGGER (всего исправлений: 1)
Ответ на: комментарий от NAY_GIGGER

Т.е. будущий «контент» уже заранее полностью известен и надо просто его запихнуть разом?

Контент не известен, известны типы контента.

Тогда, почему-бы не использовать Qt т.к. там есть QObject.

А можно пример?

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от NAY_GIGGER

Запись/чтение по индексу, range для индекса известен на момент компиляции.

MOPKOBKA ★★★★★
() автор топика

Вроде сделал, критикуйте, плюсы вообще не знаю, так что не пугайтесь.

#include <type_traits>
#include <iostream>
#include <tuple>
#include <array>

template <typename... Ts>
class tuple_array_t : public std::tuple<Ts...> {
public:
    using std::tuple<Ts...>::tuple;

    template <size_t N>
    constexpr auto&& operator[](std::integral_constant<size_t, N>) {
        return std::get<N>(*this);
    }

    [[nodiscard]] constexpr auto size() const {
        return std::integral_constant<std::size_t, std::tuple_size_v<decltype(static_cast<std::tuple<Ts...>>(*this))>>();
    }
};

template <std::size_t From, std::size_t To, class F>
constexpr void constexpr_for_range(F&& f) {
    if constexpr (From < To) {
        f(std::integral_constant<size_t, From>());
        constexpr_for_range<(std::size_t)From + 1, (std::size_t)To>(f);
    }
}

template<typename T, size_t N, typename... Ts>
constexpr auto linear_tuple_array() {
    if constexpr (N > 1) {
        return linear_tuple_array<T, N - 1, Ts..., std::array<T, N>>();
    } else {
        return tuple_array_t<Ts..., std::array<T, N>>();
    }
}

constexpr auto generate_super_array() {
    auto linear_array = linear_tuple_array<float, 10>();
    auto linear_array_size = linear_array.size();
    constexpr_for_range<0, linear_array.size()>([&] (auto i) {
        constexpr auto n = linear_array_size - i;
        constexpr_for_range<0, n>([&] (auto j) {
            linear_array[i][j] = i * 1000 + j;
        });
    });
    return linear_array;
}

int main() {
    auto array = generate_super_array();

    auto x = std::integral_constant<std::size_t, 5>();
    auto y = std::integral_constant<std::size_t, 3>();
    
    // #output: 5003
    std::cout << array[x][y] << std::endl;
}

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от SZT

Не вижу где применить, может ты не понял? Мне надо такое:

static_assert_type(GenArray(N = 3), Array{ 
  {1},
  {1, 2},
  {1, 2, 3}
})

static_assert_type(GenArray(N = 5), Array{ 
  {1},
  {1, 2},
  {1, 2, 3},
  {1, 2, 3, 4},
  {1, 2, 3, 4, 5}
})

Сам тип Array{} при N = 3 представен в коде выше как std::tuple<std::array<int, 1>, std::array<int, 2>, std::array<int, 3>>

MOPKOBKA ★★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

А, я не так понял. Если просто массивы, и их размеры идут по возрастающей, это upper triangular matrix. Наверняка можно найти какой-то готовый код для такого

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

А какую конкретно задачу ты этим собираешься решать? Эти массивы обязательно возрастают, а не убывают, и это всегда арифметическая прогрессия? Или может быть так, что в первом массиве 1 элемент, во втором 5, в третьем 3, в четвертом 10?

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от anonymous

Компилтайм всегда лучше. А не int *matrix_idx(int x, int y), потому что все равно зерокост и потом удобнее (мне). Но в коде выше ничего нету конечно, не зерокоста, не удобства.

MOPKOBKA ★★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от SZT

А какую конкретно задачу ты этим собираешься решать?

Просто интересно.

Эти массивы обязательно возрастают, а не убывают, и это всегда арифметическая прогрессия?

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

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

Что-то ты пипец намногословил. На коленках набросал:

#include <array>
using namespace std;

template <size_t sz>
struct Awrap : array<int, sz+1> {};

template <typename Iseq>
struct Array;

template <size_t ...I>
struct Array<index_sequence<I...>> : Awrap<I>... {
};

template<int N = 5>
struct A : Array<make_index_sequence<N>> {
};

int main() {
	A<3> a;
	static_cast<array<int, 2>&>(a)[0] = 2;
}

Может можно и получше сделать, это на коленках поделка. Сначала хотел заюзать fold expression, но что-то не получается развернуть их в объявлении типа.

kvpfs ★★
()
Ответ на: комментарий от MOPKOBKA

«Красиво сделать» это немного не про кресты.

Конкретно для треугольной матрицы можно сделать хитрый трюк с адресацией внутри обычного массива, и тогда не надо хранить массив массивов, а достаточно одного массива, и вычитывать отдельный элемент можно чем-то типа arr[foo(x,y)]

SZT ★★★★★
()
Ответ на: комментарий от SZT

У меня массива массивов тоже нету, такой вариант недопустим. Трюк с адресацией можно сделать и в С, мне нужен C++ Way!

MOPKOBKA ★★★★★
() автор топика

Как типичный аноним с LOR.

Массив не нужен! Простую функцию от двух (или какая там размерность массива) аргументов нельзя?

def array(x,y): x + N*y

anonymous
()
Ответ на: комментарий от kvpfs

Если нужен обход «первого измерения» в цикле, то можно через адресную арифметику. Не, тип неизвестен будет в цикле.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от kvpfs

Чет не въезжаю, можешь написать инициализацию как у меня? Что бы по x = 5, y = 3 было значение 5003. (если массив возрастает)

MOPKOBKA ★★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от anonymous

Размерность массива специфичная, он не квадратный, а треугольный. Функцией не интересно, объектом который возвращает через operator()[] array_view тоже не интересно.

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

не интересно

Интересный какой попался

anonymous
()
Ответ на: комментарий от MOPKOBKA

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

kvpfs ★★
()

массив с элементами у которых разный размер

Это не массив. У массива всегда размер элементов одинаковый. По определению. Но ты можешь сделать массив указателей/ссылок на объекты, и вот эти объекты уже могут быть разными.

no-such-file ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Тебе надо практическую задачу решить или это экзамен на учёбе? Если первое, то всё правильно подсказали про arr[foo(x,y)], это самый прозрачный и самый оптимальный вариант. Можно обернуть его в

int * operator[](size_t y) { return this->data+foo(0,y); }

firkax ★★★★★
()
Ответ на: комментарий от no-such-file

Оверхед, указатели не катят.

MOPKOBKA ★★★★★
() автор топика

Размер массива, элементов, и их порядок известен в compile-time

Есть такой вариант: сделать объект с N полями (каждое нужного типа) и массивоподобным доступом через переопределение []. Возможно tuple, как советовали, это то что тебе надо.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от no-such-file

массивоподобным доступ через переопределение []

А как перемапить индекс на поля? Через tuple я выше уже сделал.

MOPKOBKA ★★★★★
() автор топика

Че за отстой? Почему ошибка в compile-time не выбрасывается?

std::array<int, 5>()[1000] = 1;

Как мне кстати избавится от integral_constant для operator[] у моего tulpe_array_t?

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

Еще не пробовал.

В ней в массиве элемент могут быть разного типа /любой сложности/ …

1С она такая ...

В C++ многие считают, что все это

Не нужно ...
anonymous
()
Ответ на: комментарий от MOPKOBKA

перемапить индекс на поля

В компайлтайме нужно генерировать имена полей вроде i0,i1,i2 и соответственно определение [], которое при использовании будет компилять доступ к заданному полю. Как это сделать на шаблонах я хз, и бейтесь сами. У нормальных людей для этого есть lisp.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от no-such-file

У нормальных людей для этого есть lisp.

Давай соревноваться у кого короче, вооружайся CL. Я правда не знаю, позволяет ли SBCL вырезать все лишнее из .exe?

Если позволяет, то делаешь возрастающий массив N = 10, ячейки инициализурешь как X * 1000 + Y, что бы Arr[5][3] === 5003. Массив естественно должен не на списках быть, без дополнительных указателей и прочего оверхеда.

В итоге в .exe должен быть уже зашитый массив с проинициализированными ячейками, и код в main (println Arr[5][3]), что бы убедится в том что все заполненно правильно и вычисление по индексам работает и вшито тоже.

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

Давай соревноваться у кого короче, вооружайся CL

Зачем CL? Это не спортивно, там setf всемогущий и значения по-умолчанию забоксены (типа как тебе советовали c QObject, но это динамика).

возрастающий массив N = 10, ячейки инициализурешь как X * 1000 + Y, что бы Arr[5][3] === 5003

А как это относится к твоей задаче? Ну это как-бы элементарно делается с макросом и типизированным массивом.

уже зашитый массив с проинициализированными ячейками

Разумеется образ будет иметь всё заранее «проинициализированное». Это же просто снапшот.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Зачем CL? Это не спортивно, там setf всемогущий и значения по-умолчанию забоксены (типа как тебе советовали c QObject, но это динамика).

Ну там же есть наверное SplFixedArray? А локальные переменные он вроде разбоксивает, или я с LispWorks путаю.

А как это относится к твоей задаче? Ну это как-бы элементарно делается с макросом и типизированным массивом.

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

MOPKOBKA ★★★★★
() автор топика
initializer_list<initializer_list<int>> a = {{1}, {2,3}, {4,5,6}};

Не пробовал, но осуждаю.

anonymous
()

У меня такого рода вопросы решены с использованием метаданных.
По существу свой мини компилятор в run-time …

anonymous
()
Ответ на: комментарий от MOPKOBKA

Массив то у тебя должен быть как у меня, каждая строка разного размера

Т.е. тебе нужен двумерный массив одинаковых элементов, где каждая строка имеет разную длину? Так это совсем другая задача. Это массив массивов и делается элементарно хоть на Си.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Т.е. тебе нужен двумерный массив одинаковых элементов, где каждая строка имеет разную длину?

Да.

Так это совсем другая задача. Это массив массивов и делается элементарно хоть на Си.

Нет, это будет массив указателей на массивы. А мне нужен массив массивов.

MOPKOBKA ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.