Как работает BOOST_PP_SEQ_FOLD_LEFT?

Мне нужно написать макрос, который обрабатывает произвольно длинный список таких вещей, как (A)(B)(C). Если бы я мог взять зависимость Boost, я бы просто использовал один из макросов BOOST_PP_SEQ_. К сожалению, я не могу, поэтому я остаюсь, пытаясь понять, как это работает. Это вещество не очевидно.

Может ли кто-нибудь здесь написать простую, автономную реализацию, скажем, BOOST_PP_SEQ_FOLD_LEFT, чтобы я мог посмотреть? В частности, я хотел бы преобразовать:

template_(class A, class B, class C)(
    requires IsFoo<A> && IsBar<B>)(
    requires IsBaz<C>)
void frobozzle(A, B, C);

будет переписан как:

template<class A, class B, class C,
    int dummy = 0,
    std::enable_if_t<dummy == 0 && (IsFoo<A> && IsBar<B>), int> = 0,
    std::enable_if_t<dummy == 0 && (IsBaz<C>), int> = 0>
void frobozzle(A, B, C);

Может существовать произвольное число предложений requires, и каждый из них должен получить свой собственный enable_if_t. Я работаю с одним предложением requires, но я исчерпал свой препроцессор C-fu в процессе.

В порядке, чтобы предположить std-совместимый препроцессор, так как мне не нужна поддержка MSVC.

Ответ 1

Если вы добавите дополнительный набор круглых скобок в свой синтаксис, это возможно без ограничения количества "обязательных" предложений с относительно небольшим количеством макросов:

template_((class A, class B, class C)
    (requires IsFoo<A> && IsBar<B>)
    (requires IsBaz<C>)
)
void frobozzle(A, B, C);

Макросы:

#define template_(...) template_impl_ADD_END(template_impl_LIST __VA_ARGS__) >

#define template_impl_ADD_END(...) template_impl_ADD_END2(__VA_ARGS__)
#define template_impl_ADD_END2(...) __VA_ARGS__ ## _END

#define template_impl_LIST(...) template<__VA_ARGS__, int dummy = 0 template_impl_LIST_1

#define template_impl_LIST_1(...) , std::enable_if_t<dummy == 0 && template_impl_REQUIRES(__VA_ARGS__), int> = 0 template_impl_LIST_2
#define template_impl_LIST_2(...) , std::enable_if_t<dummy == 0 && template_impl_REQUIRES(__VA_ARGS__), int> = 0 template_impl_LIST_1

#define template_impl_REQUIRES(...) (template_impl_REQUIRES_ ## __VA_ARGS__)
#define template_impl_REQUIRES_requires

#define template_impl_LIST_END
#define template_impl_LIST_1_END
#define template_impl_LIST_2_END

С помощью этих макросов приведенный выше пример расширяется до:

template <class A, class B, class C,
          int dummy = 0,
          std::enable_if_t<dummy == 0 && (IsFoo<A> && IsBar<B>), int> = 0,
          std::enable_if_t<dummy == 0 && (IsBaz<C>), int> = 0>

void frobozzle(A, B, C);

Объяснение

Рассмотрим эти макросы:

#define a(x) [x] b
#define b(x) [x] a

С этими словами:

a (1) (2) (3) (4)

Вызывает "цепную реакцию" расширения следующим образом:

a (1) (2) (3) (4)
[1] b (2) (3) (4)
[1] [2] a (3) (4)
[1] [2] [3] b (4)
[1] [2] [3] [4] a

Рекурсия не допускается в препроцессоре, но этот тип цепной реакции не является рекурсией, поскольку вызов макроса происходит только после расширения предыдущего, а не во время, так как ( не является частью расширение. (Хотя, см. https://wg21.link/cwg268)

К сожалению, хотя это будет красиво петля над последовательностью (A)(B)(C), она оставит дополнительный токен в конце: имя одного из двух используемых макросов. Трюк, который я использовал, чтобы избавиться от этого, состоит в том, чтобы обернуть весь список другим вызовом макроса, который добавит (с помощью оператора concat ##) _END после полного расширения, поэтому он станет следующим:

[1] [2] [3] [4] a_END

Тогда мы можем просто избавиться от этого последнего токена, указав:

#define a_END
#define b_END

Если мы не можем обернуть весь список, нет способа узнать, когда мы достигли последнего элемента. Единственное, что происходит, это то, что последний a или b оставлен без (, который следует за ним, что означает, что он просто не будет расширяться, поскольку a и b являются макросами в стиле функции, (И мы не можем просто определить a и b для раскрытия в ничто, потому что a и b уже являются макросами, хотя они не будут расширяться без (.)

Почему два макроса?

Когда мы пытаемся вызвать цепную реакцию, как указано выше, только с одним макросом:

#define a(x) [x] a

Это не сработает:

a (1) (2) (3) (4)
[1] a (2) (3) (4) // Doesn't expand further

Это связано с тем, как работает "(бесконечная) рекурсивная защита". Если во время расширения макроса создается токен с именем расширяемого макроса, он помечен как "непередаваемый", что означает, что он никогда больше не будет расширяться. См. http://eel.is/c++draft/cpp.rescan#2

Это означает, что расширенный a отмечен как "необратимый", и наша цепная реакция останавливается прямо после первого шага. Мы избегаем этого, используя два макроса для работы над этим правилом: a(..) не будет выдавать маркер со своим именем, но только с именем другого макроса b. Расширение a заканчивается прямо там, прежде чем b будет расширяться, потому что нет ( еще после b, так как мы "внутри" расширением a. После того, как расширение выполнено, и мы больше не "внутри" a, токены пересматриваются и получается правильный вызов b: b(..). Это приведет к повторению маркера с именем a, но поскольку мы больше не находимся в расширении первого a, он не будет помечен как "непередаваемый", а цепная реакция продолжается.

Ответ 2

Хорошо, вот быстрая и грязная вещь, которую я взбивал, я думаю, что вы можете использовать:

#include <iostream>

#define LIST (1)(2)(3)(4)

#define EAT2(list)
#define EAT(list) EAT2 list
#define KEEP(x) x EAT2(
#define STRINGIFY2(x) #x
#define STRINGIFY(x) STRINGIFY2(x)

#define HEAD(list) KEEP list )
#define TAIL(list) EAT(list)
int main()
{
    std::cout << STRINGIFY(HEAD(LIST)) << std::endl;
    std::cout << STRINGIFY(TAIL(LIST)) << std::endl;
}

В принципе, вам нужно усложниться с тем, как вы вызываете макросы.
Возьмем, к примеру:

HEAD((1)(2))

расширяется до

KEEP (1)(2) )

который расширяется до

1 EAT2 ((2))

который расширяется до

1

Это не полный ответ, но я думаю, что это может быть отправной точкой для того, что вы хотите сделать.

EDIT

Теперь я понял, как BOOST.PP выполняет свои итерации, и это не очень, вы в основном вручную записываете итерации до некоторого максимального размера.

#define CONCAT2(x, y) x##y
#define CONCAT(x, y) CONCAT2(x, y)

#define SEQ_SIZE(seq) CONCAT(SEQ_SIZE_, SEQ_SIZE_0 seq)

# define SEQ_SIZE_0(_) SEQ_SIZE_1
# define SEQ_SIZE_1(_) SEQ_SIZE_2
# define SEQ_SIZE_2(_) SEQ_SIZE_3
# define SEQ_SIZE_3(_) SEQ_SIZE_4
# define SEQ_SIZE_4(_) SEQ_SIZE_5
# define SEQ_SIZE_5(_) SEQ_SIZE_6
# define SEQ_SIZE_6(_) SEQ_SIZE_7
# define SEQ_SIZE_7(_) SEQ_SIZE_8
# define SEQ_SIZE_8(_) SEQ_SIZE_9
# define SEQ_SIZE_9(_) SEQ_SIZE_10
# define SEQ_SIZE_10(_) SEQ_SIZE_11
# define SEQ_SIZE_SEQ_SIZE_0 0
# define SEQ_SIZE_SEQ_SIZE_1 1
# define SEQ_SIZE_SEQ_SIZE_2 2
# define SEQ_SIZE_SEQ_SIZE_3 3
# define SEQ_SIZE_SEQ_SIZE_4 4
# define SEQ_SIZE_SEQ_SIZE_5 5
# define SEQ_SIZE_SEQ_SIZE_6 6
# define SEQ_SIZE_SEQ_SIZE_7 7
# define SEQ_SIZE_SEQ_SIZE_8 8
# define SEQ_SIZE_SEQ_SIZE_9 9
# define SEQ_SIZE_SEQ_SIZE_10 10

#define MAKE_VAR(elem)                         \
    float CONCAT(var_, elem) = 0;

#define MAKE_LIST_0(op, list)
#define MAKE_LIST_1(op, list)  op (HEAD(list)) MAKE_LIST_0(op, TAIL(list))
#define MAKE_LIST_2(op, list)  op (HEAD(list)) MAKE_LIST_1(op, TAIL(list))
#define MAKE_LIST_3(op, list)  op (HEAD(list)) MAKE_LIST_2(op, TAIL(list))
#define MAKE_LIST_4(op, list)  op (HEAD(list)) MAKE_LIST_3(op, TAIL(list))
#define MAKE_LIST_5(op, list)  op (HEAD(list)) MAKE_LIST_4(op, TAIL(list))
#define MAKE_LIST_6(op, list)  op (HEAD(list)) MAKE_LIST_5(op, TAIL(list))
#define MAKE_LIST_7(op, list)  op (HEAD(list)) MAKE_LIST_6(op, TAIL(list))
#define MAKE_LIST_8(op, list)  op (HEAD(list)) MAKE_LIST_7(op, TAIL(list))
#define MAKE_LIST_9(op, list)  op (HEAD(list)) MAKE_LIST_8(op, TAIL(list))
#define MAKE_LIST_10(op, list)  op (HEAD(list)) MAKE_LIST_9(op, TAIL(list))

#define MAKE_LIST(op, list) CONCAT(MAKE_LIST_, SEQ_SIZE(list)) (op, list)

int main()
{
    MAKE_LIST(MAKE_VAR, LIST)
}

Запуск препроцессора при этом дает следующее:

int main()
{
    float var_1 = 0; float var_2 = 0; float var_3 = 0; float var_4 = 0; float var_5 = 0;
}

По желанию. Я уверен, что это может быть немного упрощено, но я надеюсь, что это поможет.

Ответ 3

Вот мои маленькие 2 цента:

Проблема в моем запоминании методов метапрограммирования препроцессора, используемых в Boost.Preprocessor, заключается в том, что наличие произвольного длинного списка элементов невозможно при сгибании над последовательностью.

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

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

Что бы я сделал для этого случая, если вы можете немного изменить API:

#define EXPAND(...) __VA_ARGS__

#define template_(X, Y)   \
  template<EXPAND X       \
    , int dummy = 0       \
  Y                       \
>

#define requires(...) \
  COMMA() std::enable_if_t< dummy == 0 && (__VA_ARGS__) > = 0  

#define COMMA() ,

Итак, с измененным API:

template_((class A, class B, class C),
    requires(IsFoo<A> && IsBar<B>)
    requires(IsBaz<C>)
)
void frobozzle(A, B, C);

Он выводит на желаемое:

template<class A, class B, class C , 
  int dummy = 0 , 
  std::enable_if_t< dummy == 0 && (IsFoo<A> && IsBar<B>) > = 0 ,
  std::enable_if_t< dummy == 0 && (IsBaz<C>) > = 0 >
void frobozzle(A, B, C);

Является не совсем запрошенным API, но преимущество состоит в том, что вам может потребоваться выражение с запятыми благодаря требуемым макросам, который использует VA_ARGS:

template_((class A, class B, class C),
    requires(IsBaseOf<B,C>)
)
int boo()

Я старался сделать неограниченный FOLD_LEFT, и он не выглядит в моей досягаемости: D.

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