Можно ли использовать несколько универсальных для создания строкового литерала?


Есть ли способ использовать ключевое слово _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 3

3 ответа:

Я собираюсь сказать, что ответ-нет.

Во-первых, ключевое слово _Generic не является (и не может быть) директивой препроцессора. Ageneric-selection является первичным выражением, как определено в разделе 6.5.1. Учитывая входные данные
printf(CS(abc.a) "hello", abc.a);

Выходные данные препроцессора (генерируемые параметром компилятора -E):

printf(_Generic((abc.a), int: "%d", char: "%c", long: "%ld") "hello", abc.a);
Обратите внимание, что конкатенация строк невозможна, поскольку generic-selection не был вычислен. Также обратите внимание, что это невозможно для предварительного процессора оценить, так как он требует знания, что abc является структурой типа ABC, которая имеет член a. Препроцессор делает простую подстановку текста, он не знает таких вещей. Во-вторых, фазы компилятора, определенные в разделе 5.1.1.2, не позволяют вычислять ключевые слова _Generic перед конкатенацией строк. Соответствующие фазы, приведенные в спецификации, являются
  1. Соседние строковые литеральные токены: конкатенированный.

  2. Символы пробела, разделяющие маркеры, больше не имеют значения. Каждый токен предварительной обработки преобразуется в токен. В результате чего лексемы синтаксически и семантически анализируются и переводятся как единица перевода.

Ключевое слово _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, который может интерпретировать всю структуру как одну строку. Конечно, это довольно сомнительная практика (хотя она и не нарушает строгого сглаживания): если есть какие-либо отступы, то программа не будет работать.