Trick: значения массива заполнения с использованием макросов (генерация кода)
Являются ли шаблоны С++ скрытыми только макросами?
Я читал вышеприведенную тему, и вдруг эта мысль пришла мне в голову: почему бы не попробовать написать несколько сложных макросов, которые можно использовать в нашем реальном коде (не только в виде головоломок, которые бесполезны в реальной жизни)?
Итак, первое, на что приходило в голову, это: заполнение значений массива макросами:
int f(int &i) { return ++i; }
#define e100 r5(m20)
#define m20 m5,m5,m5,m5
#define m5 r5(e1)
#define e1 f(i) //avoiding ++i right here, to avoid UB!
#define r5(e) e,e,e,e,e
int main() {
int i=0; //this is used in the macro e1
int a[] = {e100}; //filling array values with macros!
int n = sizeof(a)/sizeof(int);
cout << "count = " << n << endl;
for(int i = 0 ; i < n ; i++ )
cout << a[i] << endl;
return 0;
}
Вывод:
count = 100
1
2
3
4
.
.
.
100
Демо-версия онлайн: http://www.ideone.com/nUYrq
Можем ли мы дополнительно улучшить это решение с точки зрения компактности или общности (возможно, и того и другого)? Можем ли мы избавиться от переменной i
, которая нам нужна в макросе? Или любое другое улучшение?
Я также хотел бы знать, является ли это допустимым кодом как на С++, так и на C (конечно, игнорируя часть печати)?
EDIT:
Я понял, что порядок вызовов f()
кажется еще неопределенным. Я не уверен, хотя, как я думаю, запятая в инициализации массива не является, вероятно, такой же, как оператор запятой (в общем). Но если это так, можем ли мы избежать этого, и какая часть Стандарта говорит о его неуточнении?
Ответы
Ответ 1
Если вы хотите углубиться в программирование препроцессора, я могу рекомендовать только библиотеку Boost.Preprocessor в качестве строительного блока, вы избежите переписывать вещи с нуля.
Например, чтобы создать вашу таблицу, я бы использовал (ideone):
#include <iostream>
#include <boost/preprocessor/repetition/enum.hpp>
#define ORDER(z, n, text) n
int main() {
int const a[] = { BOOST_PP_ENUM(100, ORDER, ~) };
std::size_t const n = sizeof(a)/sizeof(int);
std::cout << "count = " << n << "\n";
for(std::size_t i = 0 ; i != n ; ++i )
std::cout << a[i] << "\n";
return 0;
}
И оставим все трещины в Boost:)
Примечание: здесь перечислены от 0 до 99, а не от 1 до 100, для выполнения арифметики доступны другие операции;)
EDIT: Как это работает?
Во-первых, я могу только рекомендовать запись doc для BOOST_PP_ENUM
BOOST_PP_ENUM
- это макрос, который принимает 3 аргумента: (n, MACRO, data)
-
n
: целое число
-
MACRO
: макрос, принимающий 3 аргумента: (z, i, data)
-
data
: некоторые данные вашего удобства будут переданы в MACRO
Затем он будет заменен на n последовательных вызовов MACRO
, разделенных запятыми:
MACRO(z, 0, data), MACRO(z, 1, data), ... , MACRO(z, n-1, data)
Вы должны делать все, что пожелаете, с помощью MACRO
.
Я боюсь, что никогда не использовал аргумент z
, он используется внутри, и вы могли бы теоретически использовать его для ускорения процесса.
Ответ 2
P99 имеет макрос, который делает именно то, что вы хотите
#include "p99_map.h"
int Ara[] = { P99_POSS(100) };
Преимущество состоит в том, что он полностью компилирует время, не имеет никакой динамической инициализации с функциями и т.д.
Для вас, вероятно, недостаток заключается в том, что он использует функции C99, в частности макросы с аргументами переменной длины.
Ответ 3
Нет, это недопустимый код; поведение по-прежнему undefined. Поскольку между элементами инициализации массива нет точек последовательности, вызовы f() могут возникать в любом порядке.
Можно создавать последовательности. Boost.Preprocessor делает это и использует такие последовательности, чтобы испускать гораздо более интересные вещи.
Ответ 4
Я думаю, что template
обеспечит превосходное решение, которое будет определенным и менее подверженным ошибкам. См. Следующий код; многие вещи вычисляются только во время компиляции, и должен генерировать эффективный код.
template<int VALUE, int INDEX, int SIZE, bool ALLOW>
struct Assign
{
static void Element (int *p)
{
Assign<VALUE + 1, INDEX + 1, SIZE, (INDEX < SIZE)>::Element(p);
p[INDEX] = VALUE;
}
};
template<int VALUE, int INDEX, int SIZE>
struct Assign<VALUE, INDEX, SIZE, false>
{
static void Element (int *p) { p[INDEX] = VALUE; }
};
template<int START, int SIZE>
void Initialize (int (&a)[SIZE])
{
Assign<START, 0, SIZE, true>::Element(a);
}
Это может быть немного сложным на первый взгляд, но понятным. Его можно сделать более общим. Использование будет таким же простым, как и следующее:
int a[100];
Initialize<1>(a); // '1' is the starting value
Это можно использовать для любого int a[N]
. Вот вывод кода.
Ответ 5
Как насчет некоторого простого генерации кода.
#include <fstream>
int main() {
std::ofstream fout("sequence_macros.hpp");
for(int i=1; i<=100; ++i)
{
fout << "#define e" << i << "(a) ";
fout << "(a+0)";
for(int j=1; j<i; ++j)
{
fout << ",(a+" << j << ")";
}
fout << '\n';
}
}
Затем используйте его:
#include <iostream>
#include "sequence_macros.hpp"
int main()
{
// Create an array with 157 elements, in
// sequence, starting at 25
int x[] = {e100(25),e50(25+100),e7(25+100+50)};
int sz = sizeof(x) / sizeof(*x);
for(int i=0; i<sz; ++i)
std::cout << x[i] << '\n';
}
Создание кода: http://www.ideone.com/iQjrj
Используя код: http://ideone.com/SQikz