Время компиляции: подсчитать # (~ N) элементов типа X и определить массив элементов [N]?

#c #arrays #struct #counter #compile-time

#c #массивы #структура #счетчик #время компиляции

Вопрос:

Есть ли способ подсчитать количество элементов в struct (целых чисел в приведенном ниже примере) во время компиляции и определить массив того же размера в той же структуре? Использование МАКРОСА или шаблонной структуры вместо типа int было бы нормально.

 struct A
{
    int myInts[numInts()];  // here numInts() == 2
    int a;
    char b;
    char c;
    int d;
    char e;
};
  

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

====== Редактировать

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

Комментарии:

1. Вы можете сделать это после определения структуры, используя различные приемы «преобразования структуры в кортеж с использованием структурированных привязок»

2. Зачем вам массив такого же размера? Расскажите подробнее о причине, по которой вы этого хотите, и, возможно, мы сможем предложить альтернативы.

3. Мне нужно компактное хранилище для копии всех целых чисел, которые являются частью структуры (и любых целых чисел внутри любых элементов подструктуры), поскольку все они копируются из и в непрерывный блок памяти одновременно. Причина, по которой все целые числа не могут быть определены вместе, заключается в том, что некоторые из целых чисел входят как дочерние элементы других структурных элементов (т.Е. struct substruct{ char a; int b;}; следовательно, они чередуются в памяти. Структура может содержать довольно много элементов, отличных от int.

Ответ №1:

Это возможно для некоторых типов с использованием библиотеки magic_get. Ограничение заключается в:

T должен быть инициализируемым агрегатом constexpr и не должен содержать ссылок или битовых полей

Структура A в вашем сообщении удовлетворяет этому условию.

magic_get определяет количество нестатических элементов данных с помощью агрегированной инициализации. В принципе, идея заключается в том, что если у вас есть какой-то тип, U который может преобразовываться в любой другой тип, то T{U(), U(), ..., U()} будет правильно сформирован, когда количество U() s равно количеству элементов в совокупности T . (Если T есть некоторые элементы, которые имеют инициализатор элемента по умолчанию или могут быть инициализированы значением, это выражение также может быть правильно сформировано, когда инициализаторов меньше, чем элементов, поэтому вам нужно выбрать максимальное количество, которое работает.) И SFINAE можно использовать для определения того, правильно ли сформировано такое выражение или нет.

Вы можете увидеть все кровавые подробности в fields_count.hpp

Комментарии:

1. Для этого потребовался бы полный класс, поэтому его нельзя использовать для определения элемента, просто для его проверки.

2. @Jarod42 Хороший момент. Я недостаточно внимательно читал

Ответ №2:

К этому можно приблизиться с помощью MAP-MACRO и некоторых шаблонов. Вам нужно определить свои структуры с помощью макроса и передать в него все поля (например. DEFINE_STRUCT(A, int a, char b, ...) ). Количество полей int (или другого нужного вам типа) можно подсчитать, применив рекурсивный шаблон к вспомогательной функции типа void(int a, char b, ...) . МАКРОС MAP необходим для добавления точек с запятой после каждого поля.

 #include <type_traits>
#include "map-macro/map.h" // https://github.com/swansontec/map-macro, we need the `MAP` macro


// Count how many arguments of Func are of type T

template<typename T, typename Func>
struct count_of_type;

template<typename T>
struct count_of_type<T, void()> { static constexpr std::size_t value = 0; };

template<typename T, typename FirstArg, typename...RestArgs>
struct count_of_type<T, void(FirstArg, RestArgs...)> {
  static constexpr std::size_t value = std::is_same<T, FirstArg>::value   count_of_type<T, void(RestArgs...)>::value;
};


// Define your structs

#define APPEND_SEMICOLON(FIELD) FIELD;

#define DEFINE_STRUCT(NAME, FIELDS, ...) 
  struct NAME { 
    int myInts[count_of_type<int, void(FIELDS, ##__VA_ARGS__)>::value]; 
    MAP(APPEND_SEMICOLON, FIELDS, ##__VA_ARGS__) 
  };

DEFINE_STRUCT(A,
    int a,
    char b,
    char c,
    int d,
    char e)

static_assert(std::is_same<decltype(A().myInts), int[2]>::value, "");

  

Комментарии:

1. Интересное решение. Я считаю, что это обеспечивает решение моей проблемы, но это не то решение, которое я могу использовать для своего варианта использования. В моем случае цель избежать необходимости указывать # вхождений типа состоит в том, чтобы снизить сложность разработки поддержки счетных сложных типов и сделать подсчет автоматическим. Однако указывать # сложных типов заранее или при изменении сложного типа лучше, чем делать структуры менее удобными в обслуживании / сложнее работать, что делает перенос всей структуры. Спасибо вам в любом случае, я думаю, что это настолько хорошо, насколько это соответствует тому, что я представлял!