Как объединить все аргументы вариативного макроса в строку в кавычках?

Как объединить все аргументы вариативного макроса в строку в кавычках?

Ниже приведено определение невариативного макроса, который объединяет 2 аргумента в строку в кавычках:

#define TO_STRING(x) #x
#define CONCAT_STRINGIFY(x,y) TO_STRING(x##y)

Вызов этого макроса следующим образом:

CONCAT_STRINGIFY(AAA,BBB)

... производит следующий вывод:

"AAABBB"

Как сделать макрос CONCAT_STRINGIFY(...) вариативным, чтобы он принимал произвольное количество аргументов?

P.S.
Решение может использовать только препроцессор C или C++ и никакие сторонние библиотеки
Кстати: я не передаю вывод препроцессора компилятору C/C++.

Это выглядит похоже: stackoverflow.com/questions/69968476/…

Barmar 08.12.2022 00:28

Ой, макро ад! Зачем вам это нужно, молитесь?

Paul Sanders 08.12.2022 00:30

@Barmar: то, что вы связали, имеет определяемый пользователем разделитель. В моем вопросе такого разделителя нет. Это делает его проще.

Pavel Stepanek 08.12.2022 00:43

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

Barmar 08.12.2022 00:44

Обратите внимание, что для вывода c/c++ вы можете просто вызывать TO_STRING() для каждого аргумента. В c/c++ "abc" "def" допустимо и совпадает с "abcdef".

lorro 08.12.2022 00:48

@Barmar: Нет, мне действительно не нравится это решение. Он имеет верхний предел для аргументов, вручную перечисляет макросы без переменных и является громоздким. Вскоре я буду использовать отдельные макросы CONCAT_STRINGIFY_n для другого количества аргументов, чем метод, показанный в другом посте. Я надеюсь, что кто-то найдет лучшее решение, воспользовавшись тем фактом, что мне не нужен «определяемый пользователем разделитель».

Pavel Stepanek 08.12.2022 00:49

Вся причина, по которой это делается таким образом, заключается в том, что это единственный способ сделать это в препроцессоре. Это не язык общего назначения, в нем нет циклов.

Barmar 08.12.2022 00:51

@lorro: решение может использовать только препроцессор C/C++. Это не для предварительной обработки кода C. Я специально указал, что он должен использовать ТОЛЬКО препроцессор. Он не может использовать компилятор C. Без компилятора соседние строки в кавычках НЕ БУДУТ объединены.

Pavel Stepanek 08.12.2022 00:51

Это рабочий хак: #define CONCAT_STRINGIFY(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V‌​,W,X,Y,Z) TO_STRING(A##B##C##D##E##F##G##H##I##J##K##L##M##N##O##P##Q#‌​#R##S##T##U##V##W##X‌​##Y##Z) но он требует традиционного препроцессора /Zc:preprocessor- и генерирует предупреждение C4003. Я не могу отключить это предупреждение в MSVC с помощью #pragma warning( disable : 4003 ), и даже если бы я мог, все директивы #pragma ... остаются в выводе после предварительной обработки, что неприемлемо, поскольку я не передаю вывод препроцессора компилятору C.

Pavel Stepanek 08.12.2022 15:25
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
9
158
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Два решения.

1.

На основе обычного шаблона COUNT, который описан в ответе на другой вопрос (в комментариях) от Jarod42:

Во-первых, вы определяете макросы подсчета аргументов. Вы можете добавить произвольное количество аргументов (до предела компилятора) в COUNT_N и числа в определении COUNT. У него будут числа в порядке убывания после __VA_ARGS__, поэтому он возвращает количество аргументов:

#define COUNT_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...)    N
#define COUNT(...)   COUNT_N(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
// Warning: COUNT() return 1 (as COUNT(A)) :-/

Тогда вам понадобятся обычные определения IDENTITY и APPLY:

#define IDENTITY(N) N
#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

Наконец, вы добавляете диспетчер на основе количества аргументов. К сожалению, вам нужно создать кейс для каждого количества аргументов; если хотите, вы можете сгенерировать этот код (да, даже с препроцессором, но в отдельном прогоне):

#define CONCAT_STRINGIFY_DISPATCH(N) CONCAT_STRINGIFY ## N

#define TO_STRING(X) #X
#define CONCAT_STRINGIFY1(A) TO_STRING(A)
#define CONCAT_STRINGIFY2(A, B) TO_STRING(A ## B)
#define CONCAT_STRINGIFY3(A, B, C) TO_STRING(A ## B ## C)
#define CONCAT_STRINGIFY4(A, B, C, D) TO_STRING(A ## B ## C## D)
// ...

#define CONCAT_STRINGIFY(...) IDENTITY(APPLY(CONCAT_STRINGIFY_DISPATCH, COUNT(__VA_ARGS__)))(__VA_ARGS__)

CONCAT_STRINGIFY(AAA, BBB, CCC)

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

#define TO_STRING(X) #X
#define CONCAT_STRINGIFY_4(A, B, C, D, ...) TO_STRING(A ## B ## C## D)
#define CS(...) CONCAT_STRINGIFY_4(__VA_ARGS__,,,,)

Это работает до 4 аргументов, вы можете добавить больше по мере необходимости.

Во втором решении CS(AAA,BBB,CCC) расширяется до "AAA,BBB,CCC". Запятые не должны быть частью расширения, поэтому это не работает. Смотрите: godbolt.org/z/o1cMq4snM

Pavel Stepanek 08.12.2022 08:50

Кстати: это тоже рабочий хак: #define CONCAT_STRINGIFY(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V‌​,W,X,Y,Z) TO_STRING(A##B##C##D##E##F##G##H##I##J##K##L##M##N##O##P##Q#‌​#R##S##T##U##V##W##X‌​##Y##Z) но он генерирует предупреждение C4003. Я не могу отключить это предупреждение в MSVC с помощью #pragma warning( disable : 4003 ), и даже если бы я мог, все директивы #pragma ... остаются в выводе после предварительной обработки, что неприемлемо, поскольку я не передаю вывод препроцессора компилятору C.

Pavel Stepanek 08.12.2022 09:55

@PavelStepanek Я думаю, это нестандартная штука для MSVC.

lorro 08.12.2022 11:41

@PavelStepanek попробуйте включить /Zc:preprocessor, чтобы получить препроцессор, соответствующий стандарту, в MSVC.

camel-cdr 08.12.2022 12:09

Да, мой уродливый хак работает только с традиционным препроцессором /Zc:preprocessor- Именно поэтому я задал этот вопрос на StackOverflow, чтобы найти более общее решение.

Pavel Stepanek 08.12.2022 15:53

@PavelStepanek Спасибо за голосование. Кроме того, чтобы уточнить, исправление camel-cdr работает для MSVC (удаляет запятые).

lorro 08.12.2022 20:06
Ответ принят как подходящий

Вот общее решение. Он работает с 342 аргументами, но может быть экспоненциально расширен для работы с большим количеством аргументов путем добавления большего количества EVAL.

#define EVAL1(...) __VA_ARGS__
#define EVAL2(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
#define EVAL5(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))

#define EMPTY()

#define TUPLE_AT_1(a,b,...) b
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__)
#define CAT_PROBE(...) ,CAT_END,

#define CAT_IND() CAT_
#define CAT_(x,a,...) CHECK(CAT_PROBE a,CAT_NEXT)(x,a,__VA_ARGS__)
#define CAT_NEXT(x,a,...) CAT_IND EMPTY()()(x##a,__VA_ARGS__)
#define CAT_END(x,a,...) #x

#define CAT(...) EVAL5(CAT_(,__VA_ARGS__,()))
CAT(a,b,c,d,e,f,g,h,i,j,k) // "abcdefghijk"

Он использует макрорекурсию для объединения всех аргументов до тех пор, пока не будет достигнут искусственно добавленный аргумент "()". Я выбрал «()», потому что его нельзя вставить и его легко обнаружить.

Поскольку MSVC по умолчанию не реализует препроцессор, соответствующий стандарту, вам необходимо включить флаг /Zc:preprocessor, чтобы приведенный выше код работал в MSVC.

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

Если вас не волнует общее решение, вот красивое и компактное решение с 16 аргументами:

#define CAT_(a,b,c,d,e,f,g,i,j,k,l,m,n,o,p,...) a##b##c##d##e##f##g##i##j##k##l##m##n##o##p
#define CAT(...) CAT_(__VA_ARGS__,,,,,,,,,,,,,,,,,)
#define STR(...) #__VA_ARGS__
#define STRe(...) STR(__VA_ARGS__)
STRe(CAT(1,2)) // "12"
STRe(CAT(1,2,3,4,5,6,7)) // "1234567"

Пожалуйста, добавьте в свой ответ отказ от ответственности, что ваши решения не работают с традиционным препроцессором MSVC (опция: /Zc:preprocessor-), и я приму ваш ответ. Смотрите: godbolt.org/z/5qexvb1Wj

Pavel Stepanek 08.12.2022 15:37

Если вам известно более общее решение, которое работает на всех типах препроцессоров, включите его.

Pavel Stepanek 08.12.2022 15:56

@PavelStepanek подойдет, но в вашем вопросе не указан msvc, и приведенное выше решение должно строго соответствовать стандарту C. Вы не можете учитывать все ошибочные реализации.

camel-cdr 08.12.2022 17:37

Вы правы, и мне придется реализовать универсальный конкатенатор для MSVC, используя #if !defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL ваш метод для нетрадиционного препроцессора и что-то вроде: #define CONCAT_STRINGIFY(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V‌​,W,X,Y,Z) TO_STRING(A##B##C##D##E##F##G##H##I##J##K##L##M##N##O##P##Q#‌​#R##S##T##U##V##W##X‌​##Y##Z) для традиционного препроцессора. К сожалению, последнее вызывает предупреждение C4003, которое нельзя отключить с помощью #pragma warning( disable : 4003 ).

Pavel Stepanek 08.12.2022 18:08

Другие вопросы по теме