LINUX.ORG.RU

Цикл для макросов

 , ,


0

3

Здравствуйте. Я сразу к перейду к примеру:

#define SS(name, val) name=val
#define CR_ENUM(name, val, ...)   \
enum E{                           \
  name=val,                       \  
  SS(__VA_ARGS__)                 \
};
 
int main() {
    CR_ENUM(wen, 1, qq, 4)
}
Т.е. мы создали перечисление, где wen = 1 и qq = 4. Отлично, но что если кол-во членов в перечислении будет больше? Хотелось бы написать что-то такое:
#define SS(name, val, ...)   \
name=val                     \
SS(__VA_ARGS__)              \

#define CR_ENUM(name, val, ...)    \
enum E{                            \
  name=val,                        \
  SS(__VA_ARGS__)                  \
};
Организавать рекурсию. Типа как у шаблонов. Важно - не привязывать жёстко к кол-ву аргументов, а сделать в ... стиле. Реально ли?

★★

Нереально. Сишные макросы не поддерживают рекурсию. Тут только перечисления могут быть. Сам над этим парился.

kachsheev ★★★
()

Вот тут чувак написал рекурсию на макросах. Подумай, хочешь ли ты быть как он? :)

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

Сишные макросы не поддерживают рекурсию.

Поддерживают, но лучше бы не поддерживали http://govnokod.ru/20958

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

Только если какой-нибудь внешний препроцессор использовать.

Я вот тоже над этим задумывался.
Как это сделать? Есть какие-то примеры? (Я надеюсь, это не ручной парсинг текстового файла, а в C для этого что-то есть?)

P. S. m4 не предлагать

Kroz ★★★★★
()

Может тебе будет достаточно приема xmacro?

four_str_sam
()

в boost.preprocessor есть BOOST_PP_REPEAT и BOOST_PP_SEQ_FOR_EACH

annulen ★★★★★
()

почему нельзя использовать

int main() {
    enum E { wen=1, qq=4, other=10 };
}

?

все равно перечисление будет создаваться на этапе прекомпиляции

zudwa
()

если нехватает возможностей встроенного препроцессора, можно использовать внешний. Кстати вот зря хают php - им можно(и весьма удобно) генерить с/h :-)

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

P. S. m4 не предлагать

Вот только хотел его назвать :)

Вообще, тут всё просто, бери любой высокоуровневый ЯП и вперёд. Вот пример с Ruby.

Препроцессор (rpp):

#!/usr/bin/env ruby

require 'erb'
require 'optparse'

load_files = []
definitions = {}

opt_parser = OptionParser.new('Usage: rpp [options] IN_FILE OUT_FILE') do |opts|
  opts.separator "\nOptions:"

  opts.on('-D NAME[=VALUE]', 'Predefine NAME with definition VALUE (defaults to true)') do |name|
    name, value, = name.strip.split('=')
    definitions[name] = value || true
  end

  opts.on('-U NAME', 'Cancel any previous definition of NAME provided with a -D option') do |name|
    definitions.delete(name.strip)
  end

  opts.on('-l FILE', '--load FILE', 'Load definitions from FILE') do |file|
    load_files << file
  end

  opts.on_tail('-h', '--help', 'Display this message') do
    puts opts
    exit
  end
end

class Context
  def initialize(definitions, load_files)
    definitions.each do |key, value|
      define_singleton_method(key) { value }
    end

    load_files.each do |file|
      instance_eval(File.read(file), file)
    end
  end

  def context_binding
    binding
  end
end

begin
  opt_parser.parse!(ARGV)

  unless ARGV.size == 2
    reason = case ARGV.size
             when 0 then 'missing positional arguments: IN_FILE OUT_FILE'
             when 1 then 'missing positional argument: OUT_FILE'
             else 'too many positional arguments'
             end
    abort(reason + "\n\n" + opt_parser.help)
  end

  in_file, out_file = ARGV

  context = Context.new(definitions, load_files)

  result = ERB.new(File.read(in_file)).result(context.context_binding)
  File.open(out_file, 'w') { |f| f.write(result) }
rescue OptionParser::ParseError => e
  abort(e.message + "\n\n" + opt_parser.help)
end

Главный файл программы до препроцессинга (main.c.erb):

#include <stdio.h>

/* Вызов макроса, см. доки к ERB */
<%= enum(enum_name, 'wen', 1, 'qq', 4) %>

void print_E_item(enum E e) {
  printf("%d\n", e);
}

int main(void) {
  print_E_item(wen);
}

Здесь значение enum_name задано с помощью опции -D препроцессора, а сам макрос enum определён в файле enum.rb, который указывается препроцессору с помощью опции -l. Всё это можно было определить внутри main.c.erb, но сделано так в качестве демонстрации.

Файл с определением “макроса” enum (enum.rb):

def render(fields)
  fields.each_slice(2).map do |(name, value)|
    value.nil? ? name.to_s : "\n  #{name} = #{value}"
  end.join(',')
end

def enum(name, *fields)
  "enum #{name} {#{render(fields)}\n};"
end

Makefile:

enum: main.o
	$(LINK.o) $^ -o $@

main.c: main.c.erb
	./rpp -D enum_name=E -l enum.rb $^ $@

clean:
	$(RM) *.o *.c enum
.PHONY: clean
theNamelessOne ★★★★★
()
Ответ на: комментарий от zudwa

Например, сгенерировать enum и функцию обхода по нему в одно действие.

CREATE_ENUM_AND_ADVANCE_FN(el1, 1, el2, 2, ...)  \
enum E {...};                                    \
int advance_fn(int cur){                         \
   int ar[] = { GET_VAL(el1, 1, el2, 2, ...) };  \
   for(...) {                                    \
      if(ar[i] == cur){                          \
         ++ i;                                   \
         break;                                  \
      }                                          \
   }                                             \
   return ar[i];                                 \
}
Спасибо за ответы, поизучаю. Вообще надеюсь, что возможности препроцессора будут расширяться в будущем. Может вообще было бы неплохо сделать гибрид шаблонов и макросов (разрешить шаблоном работать не только с типами, но и с любыми токенами). Т.е. такая конструкция была бы валидной:
template <token Q, typename T>
{
   enum E{
      Q = 1
   };
   void fn(T t) {}
}

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

Например, сгенерировать enum и функцию обхода по нему в одно действие.

В твоём случае сгодятся упомянутые выше xmacro. Положи в enum.def описание элементов enum’а

ENUM_ELEM(elem0, 9, "Element no. 0")
ENUM_ELEM(elem1, 8, "Element no. 1")
ENUM_ELEM(elem2, 7, "Element no. 2")
ENUM_ELEM(elem3, 6, "Element no. 3")
ENUM_ELEM(elem4, 5, "Element no. 4")
ENUM_ELEM(elem5, 4, "Element no. 5")
ENUM_ELEM(elem6, 3, "Element no. 6")
ENUM_ELEM(elem7, 2, "Element no. 7")
ENUM_ELEM(elem8, 1, "Element no. 8")
ENUM_ELEM(elem9, 0, "Element no. 9")

…и дальше генерируй из этого описания что угодно:

#include <stdio.h>
#include <stdbool.h>

/* generate enum */
#define ENUM_ELEM(name, value, desc) name = value,

enum E {
#include "enum.def"
};

/* stringify enum item names */
#undef ENUM_ELEM
#define ENUM_ELEM(name, value, desc) [value] = #name,

static const char *E_names[] = {
#include "enum.def"
};

const char *E_stringify(enum E item) {
  return E_names[item];
}

/* description for enum items */
#undef ENUM_ELEM
#define ENUM_ELEM(name, value, desc) [value] = desc,

static const char *E_desc[] = {
#include "enum.def"
};

const char *E_describe(enum E item) {
  return E_desc[item];
}


/* advance function */
#undef ENUM_ELEM
#define ENUM_ELEM(name, value, desc) value,

static const enum E E_values[] = {
#include "enum.def"
};

static const size_t E_size = sizeof E_values / sizeof *E_values;

bool advance_E(enum E *curr) {
  for (int i = 0; i < (int) E_size - 1; ++i) {
    if (*curr == E_values[i]) {
      *curr = E_values[i + 1];
      return true;
    }
  }
  return false;
}

int main(void) {
  /* iterate over E items */
  bool has_next = true;

  for (enum E item = elem0; has_next; has_next = advance_E(&item)) {
    printf("enum item `%s' has value `%d' and description `%s'\n",
           E_stringify(item), item, E_describe(item));
  }

  return 0;
}
theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 1)

four_str_sam, theNamelessOne, посмотрел xmacro. Да, весьма красивое решение, я как-то не догадался до этого. Мои задачи решит полностью, спасибо.

pavlick ★★
() автор топика

Я всё же не удержался от написания препроцессорной рекурссии. Задача - создать enum и функцию обхода (x macro тоже сгодятся, но хотел ведь рекурсию :) ). Получилось вот что:

// Выглядит страшно, да )). Но это универсальная заготовка
// Можно засунуть в какой-нибудь cpp_magic.h
//cpp_magic.h
#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) __VA_ARGS__
#define SECOND(a, b, ...) b
#define FIRST(a, ...) a
#define CAT(a,b) a ## b
#define EMPTY()
#define DEFER1(m) m EMPTY()
#define DEFER2(m) m EMPTY EMPTY()()
#define DEFER3(m) m EMPTY EMPTY EMPTY()()()
#define DEFER4(m) m EMPTY EMPTY EMPTY EMPTY()()()()
#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
#define PROBE() ~, 1
#define NOT(x) IS_PROBE(CAT(_NOT_, x))
#define _NOT_0 PROBE()
#define BOOL(x) NOT(NOT(x))
#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _END_OF_ARGUMENTS_() 0
#define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
#define _IF_ELSE(condition) CAT(_IF_, condition)
#define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
#define _IF_0(...)             _IF_0_ELSE
#define _IF_1_ELSE(...)
#define _IF_0_ELSE(...) __VA_ARGS__
#define MAP1(m, first, ...)          \
  m(first)                           \
  IF_ELSE(HAS_ARGS(__VA_ARGS__))(    \
  DEFER2(_MAP1)()(m, __VA_ARGS__)    \
  )()
#define _MAP1() MAP1
#define MAP2(m, first, second, ...)  \
  m(first, second)                   \
  IF_ELSE(HAS_ARGS(__VA_ARGS__))(    \
  DEFER2(_MAP2)()(m, __VA_ARGS__)    \
  )()
#define _MAP2() MAP2
// main.cpp
#include <stdio.h>
#include "cpp_magic.h"

#define CREATE_ENUM_HELPER_1(el, val)  el = val,
#define CREATE_ENUM_HELPER_2(el, val)  el,
#define CREATE_ENUM(name, ...)                                     \
  enum name{                                                       \
    MAP2(CREATE_ENUM_HELPER_1, __VA_ARGS__)                        \
  };                                                               \
  unsigned get_##name##_array(int *ar){                            \
    int temp[] = {                                                 \
    MAP2(CREATE_ENUM_HELPER_2, __VA_ARGS__)                        \
    };                                                             \
    if(ar != NULL)                                                 \
      for(unsigned i = 0;  i < sizeof(temp) / sizeof(int);  ++i){  \
        ar[i] = temp[i];                                           \
    }                                                              \
    return sizeof(temp) / sizeof(int);                             \
  };

EVAL( CREATE_ENUM(enum1, q,1, e,3, t,65, z,90) )
EVAL( CREATE_ENUM(enum2, ww,100, ss,-3, dh,21) )
struct S{
  EVAL( CREATE_ENUM(enum3, q,871, e,213, t,226) )
}s;

int main()
{
  int ar[100];

  printf("----enum1-----\n");
  get_enum1_array(ar);
  for(unsigned i = 0;  i < get_enum1_array(NULL);  ++ i)
    printf("%d\n", ar[i]);

  printf("----enum2-----\n");
  get_enum2_array(ar);
  for(unsigned i = 0;  i < get_enum2_array(NULL);  ++ i)
    printf("%d\n", ar[i]);

  printf("----enum3-----\n");
  s.get_enum3_array(ar);
  for(unsigned i = 0;  i < s.get_enum3_array(NULL);  ++ i)
    printf("%d\n", ar[i]);
}
Выхлоп printf:
 ----enum1-----
 1
 3
 65
 90
 ----enum2-----
 100
 -3
 21
 ----enum3-----
 871
 213
 226

// Сгенерированный код для emum1
// enum enum1{
//   q = 1,
//   e = 3,
//   t = 65,
//   z = 90,
// };
// unsigned get_enum1_array(int *ar){
//   int temp[] = { q, e, t, z, };
//   if(ar != NULL)
//     for(unsigned i = 0; i < sizeof(temp) / sizeof(int); ++i){
//       ar[i] = temp[i];
//     }
//   return sizeof(temp) / sizeof(int);
// };
Статья-путеводитель http://jhnet.co.uk/articles/cpp_magic. Оставляю с расчётом на то, что вернусь и скопирую, возможно. Если HELPER макрос должен принимать более двух аргументов (для 1 и 2 я написал), то добавить соответствующий MAP макрос.

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

Все равно непонятно. Для чего enum использовать как массив?

enum обычно используется для перечисления всех возможных значений _одного_ свойства какого-то обьекта/структуры, чтобы использовать понятный текстовый псевдоним вместо малопонятного числового значения при работе с этим свойством.

Не проще взять сразу массив и написать for_each с функцией do в качестве аргумента?

С точки зрения программы будет как-то так:

#include "enum_array.h"

void do(void* p){
  enum_array_t e = (enum_array_t*) p;

  print(e);
}

enum_array_t enums[]={ 1, 2, 3, 4 };

for_each(enums, sizeof(enums)/sizeof(enum_array_t), do);

Или вам шашечки, в смысле рекурсия в препроцессоре?

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

Эти изыскания скорее из спортивного интереса. Но кто знает, может и понадобится когда-нибудь, на будущее буду иметь в виду, что препроцессором можно сгенерировать всё что угодно. Но хотелось бы, чтобы подобные трюки стали более дружественными.

ЗЫ: лучше перенести обернуть в EVAL() не CREATE_ENUM(), а MAP2() (в main.cpp).

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