LINUX.ORG.RU

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

 


0

4

Нужно создать массив с элементами у которых разный размер. Размер массива, элементов, и их порядок известен в 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 ★★★★★
() автор топика
Ответ на: комментарий от anonymous2

Его еще вроде бы не перевели в компилтайм. И элементы там одного размера должны быть.

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

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

#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 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему: топик перемещен в архив.