Программно создавать статические массивы во время компиляции в C++
Во время компиляции можно определить статический массив следующим образом:
const std::size_t size = 5;
unsigned int list[size] = { 1, 2, 3, 4, 5 };
Вопрос 1 - Возможно ли с помощью различных методов метапрограммирования назначать эти значения "программно" во время компиляции?
Вопрос 2 - Если предположить, что все значения в массиве должны быть одинаковыми, можно ли выборочно назначать значения во время компиляции программным способом?
например:
const std::size_t size = 7;
unsigned int list[size] = { 0, 0, 2, 3, 0, 0, 0 };
- Решения с использованием C++0x приветствуются
- Массив может быть довольно большим, несколько сотен элементов длиной
- Массив на данный момент будет состоять только из типов POD
- Также можно предположить, что размер массива будет известен заранее, в статическом режиме во время компиляции.
- Решения должны быть на C++ (без сценариев, макросов, решений на основе pp или генератора кода, пожалуйста)
ОБНОВЛЕНИЕ: Решение Георгия Фрицше удивительно, требует небольшой работы, чтобы компилировать его на компиляторах msvc и intel, но, тем не менее, очень интересный подход к проблеме.
14 ответов
Самое близкое, что вы можете получить, - это использовать функции C++0x для инициализации локальных массивов или массивов элементов шаблонов из списка аргументов шаблона.
Это, конечно, ограничено максимальной глубиной создания шаблона и влажностью, которая на самом деле вносит заметные изменения в ваш случай.
Пример:
template<unsigned... args> struct ArrayHolder {
static const unsigned data[sizeof...(args)];
};
template<unsigned... args>
const unsigned ArrayHolder<args...>::data[sizeof...(args)] = { args... };
template<size_t N, template<size_t> class F, unsigned... args>
struct generate_array_impl {
typedef typename generate_array_impl<N-1, F, F<N>::value, args...>::result result;
};
template<template<size_t> class F, unsigned... args>
struct generate_array_impl<0, F, args...> {
typedef ArrayHolder<F<0>::value, args...> result;
};
template<size_t N, template<size_t> class F>
struct generate_array {
typedef typename generate_array_impl<N-1, F>::result result;
};
Использование для вашего 1..5
дело:
template<size_t index> struct MetaFunc {
enum { value = index + 1 };
};
void test() {
const size_t count = 5;
typedef generate_array<count, MetaFunc>::result A;
for (size_t i=0; i<count; ++i)
std::cout << A::data[i] << "\n";
}
Начиная с C++17 вы можете использовать constexpr
лямбда вызывает его на месте. Единственный "недостаток" в том, что вам придется использоватьstd::array
вместо массива в стиле c:
constexpr auto myArray{[]() constexpr{
std::array<MyType, MySize> result{};
for (int i = 0; i < MySize; ++i)
{
result[i] = ...
}
return result;
}()};
В качестве примера, как вы можете создать массив со степенью двойки:
constexpr auto myArray{[]() constexpr{
constexpr size_t size = 64;
std::array<long long, size> result{};
result[0] = 1;
for (int i = 1; i < size; ++i)
{
result[i] = result[i - 1] * 2;
}
return result;
}()};
Как видите, вы даже можете ссылаться на предыдущие ячейки массива.
Ну, ваши требования настолько расплывчаты, что с ними трудно что-либо сделать... Главный вопрос, конечно: откуда эти ценности?
В любом случае сборку в C++ можно рассматривать как 4 шага:
- Предварительная сборка: создание скрипта заголовка / источника из других форматов
- предварительная обработка
- Шаблонные экземпляры
- Собственная сборка
Если вы хотите исключить генерацию скриптов, то у вас есть 2 варианта: предварительная обработка и программирование мета-шаблонов.
Я просто не знаю, как программирование мета-шаблонов могло бы сработать здесь, потому что, насколько я знаю, невозможно объединить два массива во время компиляции. Таким образом, мы остаемся с спасителем дня: Программирование препроцессора
Я бы предложил использовать полноценную библиотеку, чтобы помочь нам: Boost.Preprocessor.
Особый интерес здесь:
Теперь, если бы мы только знали, где выбирать значения, мы могли бы привести более содержательные примеры.
Как насчет создания вложенной структуры с использованием шаблонов и преобразования ее в массив нужного типа. Приведенный ниже пример работает для меня, но у меня есть ощущение, что я либо ступаю, либо иду очень близко к неопределенному поведению.
#include <iostream>
template<int N>
struct NestedStruct
{
NestedStruct<N-1> contained;
int i;
NestedStruct<N>() : i(N) {}
};
template<>
struct NestedStruct<0>
{
int i;
NestedStruct<0>() : i(0) {}
};
int main()
{
NestedStruct<10> f;
int *array = reinterpret_cast<int*>(&f);
for(unsigned int i=0;i<10;++i)
{
std::cout<<array[i]<<std::endl;
}
}
И, конечно, вы можете утверждать, что массив не инициализируется во время компиляции (что, я думаю, невозможно), но значения, которые попадут в массив, вычисляются во время компиляции, и вы можете обращаться к ним, как если бы это был обычный массив. Я думаю, что это так близко, как вы можете получить.
Иногда (не всегда) такой массив генерируется из массива типов. Например, если у вас уже есть список классов переменных (например, шаблон) и вы хотите сохранить инкапсулированное значение uint32_t, вы можете использовать:
uint32_t tab[sizeof(A)]= {A::value...};
Просто используйте генератор кода. Создайте один или несколько шаблонов, которые могут генерировать нужный код, используя таблицу или даже математические функции. Затем включите файл, который вы создали в вашем приложении.
Серьезно, генератор кода сделает вашу жизнь намного проще.
Что-то вроде Boost.Assignment может работать для стандартных контейнеров. Если вам действительно нужно использовать массивы, вы можете использовать их вместе с Boost.Array.
Вы действительно должны сделать это во время компиляции? Это было бы намного проще сделать во время статической инициализации. Вы могли бы сделать что-то вроде этого.
#include <cstddef>
#include <algorithm>
template<std::size_t n>
struct Sequence
{
int list[n];
Sequence()
{
for (std::size_t m = 0; m != n; ++m)
{
list[m] = m + 1;
}
}
};
const Sequence<5> seq1;
struct MostlyZero
{
int list[5];
MostlyZero()
{
std::fill_n(list, 5, 0); // Not actually necessary if our only
// are static as static objects are
// always zero-initialized before any
// other initialization
list[2] = 2;
list[3] = 3;
}
};
const MostlyZero mz1;
#include <iostream>
#include <ostream>
int main()
{
for (std::size_t n = 0; n != 5; ++n)
{
std::cout << seq1.list[n] << ", " << mz1.list[n] << '\n';
}
}
Вы можете выдвинуть списки за пределы структур, если хотите, но я подумал, что это немного чище, как это.
Со временем возможности функций, методов и лямбда-выражений в C++ значительно улучшились. В C++ 17 вы можете использовать циклы for и условия if для фактического вычисления содержимого массива во время компиляции. См. Этот пример для сита простых чисел:
#include <array>
#include <cmath>
template<unsigned N>
constexpr auto primesieve() {
std::array<bool, N+1> primes {};
// From C++20, the init loop may be written as: primes.fill(true);
for(unsigned n = 0; n <= N; n++) {
primes[n] = true;
}
unsigned maxs = sqrt(N);
for(unsigned n = 2; n <= maxs; n++) {
if(primes[n]) {
for(unsigned j = n + n; j <= N; j += n) {
primes[j] = false;
}
}
}
return primes;
};
extern constexpr std::array<bool, 20> myprimes { primesieve<19>() };
Когда вы посмотрите на вывод сборки этого кода, вы увидите только байты данных
Однако, как уже писали другие: интерпретация кода C++ в компиляторе намного медленнее, чем выполнение скомпилированного кода C++. Таким образом, эти инициализации, которые можно разумно выполнить во время компиляции, потребуют не более нескольких миллисекунд во время выполнения.
Но
И возможности продолжают улучшаться. C++ 20 даже добавляет поддержку
1 вопрос Вы можете сделать это так.
template <int num, int cur>
struct ConsequentListInternal {
enum {value = cur};
ConsequentListInternal<num-1,cur+1> next_elem;
};
template <int cur>
struct ConsequentListInternal<0, cur> {
enum {value = cur};
};
template <int v>
struct ConsequentList {
ConsequentListInternal<v, 0> list;
};
int main() {
ConsequentList<15> list;
return 0;
}
массив <int, SIZE> t
Как уже упоминалось, с C ++ 17 вы можете использовать constexpr
vector<int> countBits(int num) {
static constexpr int SIZE = 100000;
static constexpr array<int, SIZE> t {[]() constexpr {
constexpr uint32_t size = SIZE;
array<int, size> v{};
for (int i = 0; i < size; i++)
v[i] = v[i>>1] + (i & 1); // or simply v[i] = __builtin_popcount(i);
return v;}()};
vector<int> v(t.begin(), t.begin() + num + 1);
return v;
}
Однако вам придется использовать тип массива c ++.
int t[РАЗМЕР]
Если вы действительно хотите использовать массив C
int [SIZE]
, отличается от
array<int, SIZE>
используйте следующий трюк:
Объявите глобальный массив, а затем вычислите значения внутри основного для создания статического массива во время компиляции:
int w[100000] = {0};
vector<int> countBits(int num) {
vector<int> v(w, w + num + 1);
return v;
}
int main(void) {
for (int i = 0; i < 100000; i++)
w[i] = __builtin_popcount(i);
}
Полученные результаты
Вывод во время выполнения (действительно ужасный):
OK ( 591 cycles) 0,1,1, -> 0,1,1,
OK ( 453 cycles) 0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK ( 455 cycles) 0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...
Средний результат с массивом constexpr:
OK ( 1 cycles) 0,1,1, -> 0,1,1,
OK ( 2 cycles) 0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK ( 24 cycles) 0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...
Средний вывод со вторым методом (немного быстрее, поскольку мы избавляемся от накладных расходов на массив C ++):
OK ( 0 cycles) 0,1,1, -> 0,1,1,
OK ( 1 cycles) 0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK ( 23 cycles) 0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...
Контрольный показатель
Я тестировал:
#include <vector>
#include <string>
#include <cstdint>
#include <array>
#include <iostream>
#include <ctime>
#include <iterator>
#include <sstream>
using namespace std;
vector<int> nums = {2, 5};
vector<vector<int>> expected = {{0,1,1}, {0,1,1,2,1,2}}; // feel free to add more tests
for (int i = 0; i < expected.size(); i++) {
clock_t start = clock();
vector<int> res = countBits(nums[i]);
double elapsedTime = (clock() - start);
printf("%s \033[30m(%4.0lf cycles)\033[0m\t %s -> %s\n", (expected[i] == res) ? "\033[34mOK" : "\033[31mKO", elapsedTime, toString(res).c_str(), toString(expected[i]).c_str());
}
От повышения,
boost::mpl::range_c<int,1,5>
Создает список отсортированных чисел от 1 до 5 во время компиляции. Во-вторых, вы не упоминаете никаких критериев, по которым значения будут изменены. Я почти уверен, что вы не можете отменить определение, а затем переопределить новую переменную после создания списка.
Использовать рекурсивный шаблон
template<uint64_t N>
constexpr uint64_t Value()
{
return N + 100;
}
// recursive case
template<uint64_t N, uint64_t... args>
struct Array : Array<N - 1, Value<N - 1>(), args...> {
};
// base case
template<uint64_t... args>
struct Array<0, Value<0>(), args...> {
static std::array<uint64_t, sizeof...(args) + 1> data;
};
template<uint64_t... args>
std::array<uint64_t, sizeof...(args) + 1> Array<0, Value<0>(), args...>::data = {Value<0>(), args...};
int main()
{
Array<10> myArray;
for (size_t i = 0; i < myArray.data.size(); ++i) {
cout << myArray.data[i] << endl;
}
return 0;
}
Есть много вещей, которые вы можете сделать с метапрограммированием. Но сначала я хотел бы спросить: почему вы хотите сделать это в вашем случае? Я мог бы понять, нужно ли вам объявлять такой массив в разных местах, чтобы он требовал переписывать одни и те же вещи несколько раз. Это твой случай?
Говоря "определите программно", я предлагаю следующее:
#define MyArr(macro, sep) \
macro(0) sep \
macro(0) sep \
macro(2) sep \
macro(3) sep \
macro(0) sep \
macro(0) sep \
macro(0)
К настоящему времени мы определили все значения, которые вы хотели, самым абстрактным образом. Кстати, если эти значения действительно что-то значат для вас - вы можете добавить это в объявление:
#define MyArr(macro, sep) \
macro(0, Something1) sep \
macro(0, Something2) sep \
// ...
Теперь давайте вдохнем жизнь в вышеприведенную декларацию.
#define NOP
#define COMMA ,
#define Macro_Count(num, descr) 1
#define Macro_Value(num, descr) num
const std::size_t size = MyArr(Macro_Count, +);
unsigned int list[size] = { MyArr(Macro_Value, COMMA) };
Вы также можете справиться с ситуацией, когда большинство ваших записей массива одинаковы, с некоторым извращенным творчеством:)
Но вы всегда должны спросить себя: стоит ли это того? Потому что, как вы видите, вы превращаете код в головоломку.