Можно ли использовать несколько универсальных для создания строкового литерала?
Есть ли способ использовать ключевое слово _Generic
несколько раз в одном и том же выражении для создания одного строкового литерала?
То, что я ищу, - это способ, например, генерировать одну строку формата для передачи в printf
, со всеми спецификаторами преобразования, адаптированными к соответствующим типам.
При написании этого ответа я закончил довольно уродливым обходом:
#include <stdio.h>
typedef struct {
int a;
char b;
long c;
} ABC;
// printf conversion specifiers:
#define CS(x)
_Generic((x),
int: "%d",
char: "%c",
long: "%ld")
int main (void)
{
ABC abc = {1, 'a', 2};
printf(CS(abc.a), abc.a); printf(" ");
printf(CS(abc.b), abc.b); printf(" ");
printf(CS(abc.c), abc.c); printf(" ");
return 0;
}
6 printf
звонки вместо 1, вряд ли идеальный вариант.
Проблема в том, что я не могу найти способ комбинируйте _Generic
и конкатенацию строковых литералов с помощью препроцессора, например:
printf(CS(abc.a) " ", abc.a); // doesnt work
printf(CS(abc.a) CS(abc.b), abc.a, abc.b); // doesnt work either
Потому что, по-видимому, универсальные макросы не считаются строковыми литералами в предпроцессоре, поэтому конкатенация строковых литералов невозможна. Я поиграл с макросами "stringification", но там не повезло.
3 ответа:
Я собираюсь сказать, что ответ-нет.
Во-первых, ключевое слово_Generic
не является (и не может быть) директивой препроцессора. Ageneric-selection является первичным выражением, как определено в разделе 6.5.1. Учитывая входные данныеprintf(CS(abc.a) "hello", abc.a);
Выходные данные препроцессора (генерируемые параметром компилятора
-E
):Обратите внимание, что конкатенация строк невозможна, поскольку generic-selection не был вычислен. Также обратите внимание, что это невозможно для предварительного процессора оценить, так как он требует знания, чтоprintf(_Generic((abc.a), int: "%d", char: "%c", long: "%ld") "hello", abc.a);
abc
является структурой типаABC
, которая имеет членa
. Препроцессор делает простую подстановку текста, он не знает таких вещей. Во-вторых, фазы компилятора, определенные в разделе 5.1.1.2, не позволяют вычислять ключевые слова_Generic
перед конкатенацией строк. Соответствующие фазы, приведенные в спецификации, являются
Соседние строковые литеральные токены: конкатенированный.
Символы пробела, разделяющие маркеры, больше не имеют значения. Каждый токен предварительной обработки преобразуется в токен. В результате чего лексемы синтаксически и семантически анализируются и переводятся как единица перевода.
Ключевое слово
_Generic
должно быть оценено в фазе 7, так как оно требует знания, которое доступно только после синтаксического и семантического анализа лексем, например, чтоabc
является структурой с членомa
. Следовательно, несколько ключевых слов_Generic
не могут использовать преимущества конкатенации строк для получения одного строкового литерала.
Хороший вопрос, вы можете вставить строку, передающую другой параметр:
#include <stdio.h> typedef struct { int a; char b; long c; } ABC; // printf conversion specifiers: #define CS2(x, y) \ _Generic((x), \ int: "%d" y, \ char: "%c" y, \ long: "%ld" y) int main (void) { ABC abc = {1, 'a', 2}; printf(CS2(abc.a, "Hello"), abc.a); return 0; }
Просто для записи, оказывается, можно генерировать строковую константу на основе
Решение, которое я придумал, настолько уродливо, что я едва осмеливаюсь опубликовать его, но я сделаю это, чтобы доказать, что это возможно._Generic
во время компиляции, используя другие грязные трюки, чем те, которые доступны из препроцессора.не пишите такой код!
#include <stdio.h> typedef struct { int a; char b; long c; } ABC; // printf conversion specifiers: #define CS(x) \ _Generic((x), \ int: "%d", \ char: "%c", \ long: "%ld") #pragma pack(push, 1) #define print2(arg1,arg2) \ { \ typedef struct \ { \ char arr1 [sizeof(CS(arg1))-1]; \ char space; \ char arr2 [sizeof(CS(arg2))-1]; \ char nl_nul[2]; \ } struct_t; \ \ typedef union \ { \ struct_t struc; \ char arr [sizeof(struct_t)]; \ } cs2_t; \ \ const cs2_t cs2 = \ { \ .struc.arr1 = CS(arg1), \ .struc.space = ' ', \ .struc.arr2 = CS(arg2), \ .struc.nl_nul = "\n" \ }; \ \ printf(cs2.arr, arg1, arg2); \ } #pragma pack(pop) int main (void) { ABC abc = {1, 'a', 2}; print2(abc.a, abc.b); print2(abc.a, abc.c); print2(abc.b, abc.c); return 0; }
Вывод:
1 a 1 2 a 2
Пояснение:
Макрос
print2
является оболочкой вокруг printf и выводит ровно 2 аргумента, независимо от типа, с их корректными спецификаторами преобразования.Он строит строку на основе структуры,в которую передаются строковые литералы спецификатора преобразования. Каждый держатель места массива для такого спецификатора преобразования был преднамеренно объявлен слишком маленьким, чтобы соответствовать нулевому завершению.
Наконец, эта структура сбрасывается вunion
, который может интерпретировать всю структуру как одну строку. Конечно, это довольно сомнительная практика (хотя она и не нарушает строгого сглаживания): если есть какие-либо отступы, то программа не будет работать.