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)
Ответ на: комментарий от anonymous

Напиши рантайм итерирование по «массиву» с элементами разных типов.

Так постепенно некоторые поймут что такое метаданные и какая от них польза …

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

Я его не разбирал подробно, но tuple не позволяет итерацию в ран-тайме (в цикле).

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

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

Почему? Есть макрос __TIME__. Его можно использовать как начальное значение, для простого псевдогенератора. Там все функции сделать constexpr, и вот у нас ранд в компайл тайм. При каждой компиляции разное значение…

подробнее: https://youtu.be/rpn_5Mrrxf8

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

плюсы вообще не знаю

Какое трогательное кокетство.

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

Есть макрос TIME

Может сразу компилятор запускать с правильным значением рандома gcc -DRAND=4?

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

Может сразу компилятор запускать с правильным значением рандома gcc -DRAND=4?

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

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

И ничего у тебя там не получилось, ты не сможешь итерироваться ни в компайл тайме, ни в рантайме тем более. Сможешь лишь обращаться передавая константы в качестве индексов. Идентичный функционал тому, что я постил выше, но в 100500 строк.

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

ты не сможешь итерироваться ни в компайл тайме

???

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;
        });
    });

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

макрос __TIME__ почти также работет

random в compile time.

Это не рандом

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

Ок, через какие-то хелперы пробегаешь в компайлтайме, но рантайм здесь при чём? Можно, конечно, нагенерить опять каких-нибудь специализаций хелперов под каждое возможное значение, но я хз, зачем так упарываться, пару косвенных обращений не надо, а раздуть бинарь вагоном специализаций - норм.

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

Покажи пример того, как от пользователя (из stdin) приходит число Х, и ты пробегаешь он 0 до Х и присваиваешь всем подмассивам число 5. Диапазон х от 0 до 1e6.

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

Так я же специально уточнил - итерироваться надо лишь в компалтайме или и в рантайме тоже? Ты сказала - там и там.

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

Ошибся, с тем что у меня можно в рантайме итерироваться, да. Но в идеале хотелось бы и в рантайме уметь, пусть и с ограниченной длинной.

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

Так сделайте вектора со стековым заёмщиком памяти. Касательно времени компиляции - вы уверены, что не будете менять данные ? Т.е. точно они будут только для чтения ? Если нет, то всё равно данные будут скопированы и тогда разницы с предложенным стековым заёмщиком по сути и нет.

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

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

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

Других способов как бы и нет. Компилятор должен знать, где элементы массива начинаются, значит нужны указатели на их начало. Что бы там не нагородили в С++, память то она все равно одна.

anonymous
()
Ответ на: комментарий от anonymous
#include <stdio.h>
#include <stdlib.h>

void print_5_7(int a[10][10]) {
  printf("%d\n", a[5][7]);
}

int main() {
  int **a = malloc(sizeof(int*) * 10);
  for(int i = 0;i < 10;i++) {
    a[i] = malloc(sizeof(int) * 10);
    for(int j = 0;j < 10;j++) a[i][j] = 666;
  }

  a[5][7] = 10;

  // #stdout: 666 
  print_5_7(a);
}
MOPKOBKA ★★★★★
() автор топика

Если хочется доступ к элементам во время компиляции, то не надо использовать operator[].

#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;
}
iliyap ★★★★★
()

Такой способ плох тем, что

Эта песня хороша.  
Начинай с начала ...
anonymous
()

Я ещё вот о чём подумал - а как перекинуть значение из компайлтайма в рантайм? Например, во время конфигурации скидываю содержимое некоторых файлов из proc, sys, etc в хидер с содержимым:

constexpr char proc[] = "...";
constexpr char mem[] = "...";

далее парсинг этого в компайл тайме с заносом результатов в структуру:

struct System_info {
   ...
};
consteval config() {
   System_info si;
   ...
}

и тут вопрос - а как передать копию si в рантайм? Я попробовал с глобальным интом, рантайм не видит модификациии, которые я сделал в constexpr (гцц, шланг вообще не позволяет писать в глобал).

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

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

то не надо использовать operator[].

А может просто его определить?

#include <iostream>

constexpr size_t sum_ap(size_t N) { return N * (N + 1) / 2; }

template <typename T, size_t N>
class MyArray {
public:
  constexpr MyArray() : m_data(){};
  constexpr T *operator[](size_t row) { return &m_data[sum_ap(row)]; }
  constexpr const T *operator[](size_t row) const { return &m_data[sum_ap(row)]; }

private:
  T m_data[sum_ap(N + 1)];
};

template <typename T, size_t N>
constexpr MyArray<T, N> GenMyArray() {
  MyArray<T, N> A;
  for (size_t i = 0; i < N; ++i) {
    for (size_t j = 0; j < i + 1; ++j) {
      A[i][j] = (i + 1) * 10 + (j + 1);
    }
  }
  return A;
}

template <typename T, size_t N>
void print(const MyArray<T, N> &A) {
  for (size_t i = 0; i < N; ++i) {
    for (size_t j = 0; j < i + 1; ++j) {
      std::cout << A[i][j] << " ";
    }
    std::cout << "\n";
  }
}

int main() {
  constexpr auto A = GenMyArray<int, 5>();
  static_assert(A[0][0] == 11);
  static_assert(A[1][1] == 22);
  static_assert(A[2][2] == 33);
  print(A);
  return 0;
}
AlexVR ★★★★★
()
Ответ на: комментарий от anonymous

С таким кодом легко, тут же просто массив и индексы, но с ними не интересно, так можно и до такого дойти!

#define AP_SUM(N) (N * (N + 1) / 2)
#define AP_IDX(X, Y) (AP_SUM(X) + Y)

float arr[AP_SUM(15)];
arr[AP_IDX(5, 3)] = 5003;

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

но с ними не интересно,

«вам шашечки или ехать?»

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

// #stdout: 666

Удивительно при «warning: passing argument 1 of ‘print_5_7’ from incompatible pointer type»

А вообще (не)надо так:

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

void fill(const size_t N, double A[N][N])
{
    for (size_t i = 0; i < N; ++i) {
        for (size_t j = 0; j < N; ++j) {
            A[i][j] = 10 * i + j;
        }
    }
}

void print(const size_t N, double A[N][N])
{
    for (size_t i = 0; i < N; ++i) {
        for (size_t j = 0; j < N; ++j) {
            printf("%4.1f ", A[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    for (size_t N = 5; N < 10; ++N) {
        double(*A)[N][N] = calloc(1, sizeof(*A));
        printf("Created matrix with size %d\n", (int)sizeof(*A));
        fill(N, *A);
        print(N, *A);
        free(A);
    }
    return 0;
}
AlexVR ★★★★★
()
Ответ на: комментарий от AlexVR

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

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

Так сохрани результат не в constexpr переменную. Только вот оптимизатору доказать необходимость лишней работы будет сложно.

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

Так сохрани результат не в constexpr переменную. Только вот оптимизатору доказать необходимость лишней работы будет сложно.

Речь об объекте о котором компилятор ни чего не знает …

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

Ну так std::vector в зубы и в перёд. В 20-х плюсах он ещё и constexpr.

Ну или через наследование провернуть.

Тут кому как интересней.

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

Ну или через наследование провернуть

Еще раз.
В run-time создать объект о котором компилятор ни чего не знает …

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

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

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

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

Дядь, а чем твой тензор отличается от многомерного массива? А двумерная картинка это матрица или вектор?

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

Чушь у тебя в голове, бездарь

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

Для особо умных: a[5] и *(a+5) это одно и то же.

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

И если ты отсюда делаешь вывод, что

В C имя массива является указателем на его первый элемент.

то тебе пора за парту. Учить логику.

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