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)

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

Так это же скриптовый язык, а мне сейчас zero-cost и скорость важна.

Все можно сделать, но ваш вопрос скорее ТЕМА к обсуждению и многих иных подобных вопросов …

МОЛОДЕЦ!  ...
anonymous
()
Ответ на: комментарий от MOPKOBKA

Я вижу тут не вектор из элементов переменного размера, я вижу тут 2D массив треугольной формы из одинаковых элементов. Это кладется в обычный вектор или std::array поверх которого делается индексная арифметика, в тч в компацл тайм.

Сумму арифметической прогрессии проходят в школе - она тут как понадобится.

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

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

Обычно соревнуются у кого длиннее…

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

Индексная арифметика не интересует, все должно быть представленно объектами, с ними проще работать, их проще расширять. Вот я могу уже в своем кривом примере сделать каждую строку размера rand().

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

Сумму арифметической прогрессии проходят в школе - она тут как понадобится.

Да нет же.
Он хочет, чтобы элементы массива были любого типа.
Вообще то в качестве ЛАБЫ вопрос не сложный …

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

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

Кстати, да, вполне разумный вариант, если нужен минимум оверхеда. Другой вариант (более строгий по синтаксису и «естественный» по проверкам, но более громоздкий по реализации) — контейнер внутри других контейнеров, что-то похожее предложили выше.

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

Да не, мне одного типа пока что достаточно. Сделать любого типа просто - linear_tuple_array<std::variant<...>, 10>

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

массив массивов

Массив массивов это и есть с указателями. Без указателей это двумерный массив и строки в нём одинаковые. Если ты хочешь разные строки и без указателей, то так не получится. Потому что не всё можно вычислить во время компиляции. Представь, что ты хочешь рандомный элемент, условно a[random()][2]. Тебе в рантайме придётся вычислять адрес, а как ты это сделаешь, если не знаешь где начинается каждая строка? Даже если ты сможешь избежать явного использования указателей, то компилятор всё равно захардкодит косвенное обращение. Поэтому не мучай жопу, бери массив массивов.

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

элементы массива были любого типа

Еще один интересный попался.

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

вполне разумный вариант, если нужен минимум оверхеда

Это неразумный вариант с изобретением указателей на коленке. Если нужна компактность, то можно выделить суммарный объём и внутри него проинициализировать указатели на строки.

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

Массив массивов это и есть с указателями.

Нет, int a[5][5]; в С это плоский массив, с умной индексацией, а не массив массивов.

Если ты хочешь разные строки и без указателей, то так не получится.

У меня уже получилось.

Представь, что ты хочешь рандомный элемент, условно a[random()][2]

За пределами компилтайма я его естественно не получу, но у меня такой задачи и нету, в a{I} мне I всегда известен.

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

мне I всегда известен

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

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

Он хочет, чтобы элементы массива были любого типа.

Нет, судя по коду в ОП, он хочет не этого.

Но std::variant, если что, в C++ тоже есть (появился, правда, недавно).

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

За пределами компилтайма

Кто сказал, что constexpr - это только компайлтайм?

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

компилятор бы вычислил

компилятор не обязан это вычислять и может все эти вычисления запихнуть в рантайм.

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

массив указателей на массивы уже не вычисляются

Вычисляются. Если ты выделяешь общую область (base) и потом инициализируешь указатели как

a[0] = base
a[1] = base + 42
a[2] = base + 9000
...

То при обращении a[1][105] компилятор может вычислить адрес как base + 42 + 105, т.е. прямо, без обращения к a[1]. При условии, как ты говоришь, что индексы известны в компайлтайме.

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

Ты что такое говоришь: если на написано constexpr, компилятор ничего не считает? А вот если написано constexpr, то все, компилятор любой (бесконечный) цикл за 4 секунды посчитает?

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

указатели уже должны быть проинициализированны

Они и будут проинициализированы, если ты область выделяешь в компайлтайме, типа

int base[100500];
int* a[10] = [base, base+42, base+9000 ...];
no-such-file ★★★★★
()
Ответ на: комментарий от eternal_sorrow

Потому что я никогда очень тесно не работал с этим и не могу поручиться что это точно подойдёт.

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

Движок автодополнения (clang) вычислять не обязан, а gcc начнет, поставим N = 250 для примера.

$ g++ main.cpp -Ofast -std=c++2a -ftemplate-depth=100000 -fconstexpr-depth=100000 -fconstexpr-ops-limit=100000000 -S

$ wc -l main.s
72 main.s

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

А как узнать сколько выделять

Тебе же размеры известны. Сложи, как в первом классе. Выражение константное, использовать в объявлении массива можно.

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

Создался массив, там что то подсчиталось рандомно (рандом компилтаймный), и по итогу результата создался новый массив с каким то там размером.

Создавать на каждый новый массив по base? Можна. Но проще как у меня.

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

Кароче, хочется тупл туплов туплов… Но только не спрашивайте: как буду работать с элементами это тупла: какого они типа, как их использовать в коде? Просто хачу!

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

Там проблема что нужно расширять base если он будет общий. Если на каждый массив свой то без проблем.

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

Индексную арифметику никто не мешает завернуть в обьекты. Собственно именно так это и делается для всяких симметричных тензоров и пр. Это просто вот оно 1:1

Но если в школе не учился и про сумму арифметической прогрессии не слышал то приходится страдать с std::tuple… А что там кстати у древних русов про суммы прогрессий говорили?

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

Индексную арифметику никто не мешает завернуть в обьекты

Да можно и обычные указатели в объекты заворачивать, только зачем если можно пользоваться умными?

Собственно именно так это и делается для всяких симметричных тензоров и пр.

Да то как это делается сейчас (всегда) я и так знаю.

Но если в школе не учился и про сумму арифметической прогрессии не слышал то приходится страдать с std::tuple… А что там кстати у древних русов про суммы прогрессий говорили?

У древних русов каждая строка может иметь rand() длинну, давай ка мне прогрессию для такого массива напиши.

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

Таки шо, умный указатель это не обычный указатель завернутый в обьект?!

Как это делается тензоров Вы знать не можете, потому что Вы не знаете что такое тензор и никогда не решали таких задач.

Про ранд в компайл тайм повеселило!

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

компайл тайм рандом

def rand(): 4
anonymous
()
Ответ на: комментарий от AntonI

Таки шо, умный указатель это не обычный указатель завернутый в обьект?!

Все так, но кода и багов меньше.

Как это делается тензоров

Я про индексацию.

Про ранд в компайл тайм повеселило!

В компилтайме еще можно и файлы читать, но это уже не в плюсах.

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

А программы генерирую программами? Я даже не знаю, какой этап называть компиляцией.

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

Матрица 2D, а тензор может иметь большую размерность.

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

Аналогичная история с пирамидой Паскаля. Причем размеры этих вещей известны в компайл тайм и там очень жесткие требования по памяти и скорости работы.

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

Я про индексацию.

Знали бы - не создавали тему.

В компилтайме еще можно и файлы читать, но это уже не в плюсах.

Можно и длину мпх в градусах мерять если градусник приложить(с)

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

Знали бы - не создавали тему.

Мне интересно на объектах.

Можно и длину мпх в градусах мерять если градусник приложить(с)

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

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

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

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

Мне интересно на объектах.

Ну тады фперет! Кода будет сильно больше (в тч при использовании).

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

За пределами компилтайма я его естественно не получу, но у меня такой задачи и нету

Уточни, пожалйста, по первому измерению (старшему, которое массив массивов) ты собираешься итерироваться в компайлтайме или в рантайме?

kvpfs ★★
()
Когда я был маленьким, я тоже отдыхал в Си.  
Нам тогда приходилось туго: спали в самодельных векторах,  
готовили пищу на struct,  
сами таскали воду,  
сами стирали.   
Очень были не устроены в бытовом отношении. 

А теперь оглянитесь вокруг – какие стандарты понастроили, какие классы разбили!  
Class, template, compile-time, ... и иные мероприятия…   

Дети вы хозяева C++.  
ВЫ!  
``
anonymous
()
Ответ на: комментарий от MOPKOBKA

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

for (int i ...) {
   arr[i][...] = x;
}

при i==1 тип arr[…] это один тип, а при i==2 совсем другой тип. Может в какой скриптухе такие фокусы норм, но это не про плюсы.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.