4

Is there a way to use the _Generic keyword multiple times in the same expression to create a single string literal?

What I am looking for is a way to for example generate a single format string to pass to printf, with all the conversion specifiers adapted to the proper types.

When writing this answer I ended up with a rather ugly work-around:

#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 calls instead of 1, hardly ideal.

The problem is that I can't find a way to combine _Generic and string literal concatenation by the pre-processor, like this:

printf(CS(abc.a) " ", abc.a); // doesnt work
printf(CS(abc.a) CS(abc.b), abc.a, abc.b); // doesnt work either

Because apparently generic macros don't count as string literals in the pre-processor, so string literal concatenation isn't possible. I toyed around with "stringification" macros but no luck there.

Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    Yes, `_Generic` is a primary expression and is only evaluated in a later compilation phase, after the string literal fusion has been performed. The reason for that is simple, `_Generic` needs type information that isn't available in the preprocessor phase. – Jens Gustedt Dec 21 '15 at 10:33
  • @JensGustedt Ah, so it is simply not possible then. That's too bad... I suppose I'll have to come up with some other solution which isn't based on the pre-processor, yet is evaluated at compile time. – Lundin Dec 21 '15 at 10:39
  • In P99 I have kind of solved this the other way around. The type generic stuff is done on the evaluation of the `...` arguments and transforms them into calls to formating functions that all return a string. In the format string itself you then only have to place `%s`. But in its current form this is quite hacky. gcc 4.9 handles it very badly and blows up compilation to a memory footprint that sometimes exceeds the 2GiB barrier. – Jens Gustedt Dec 21 '15 at 11:21

3 Answers3

3

I'm going to say that the answer is NO.

First, the _Generic keyword is not (and cannot possibly be) a pre-processor directive. A generic-selection is a primary expression, as defined in section 6.5.1. Given the input

printf(CS(abc.a) "hello", abc.a);

the output from the preprocessor (generated by the -E compiler option) is:

printf(_Generic((abc.a), int: "%d", char: "%c", long: "%ld") "hello", abc.a);

Notice that string concatenation is not possible because the generic-selection has not been evaluated. Also note that it's impossible for the pre-processor to evaluate since it requires knowledge that abc is a structure of type ABC, that has member a. The pre-processor does simple text substitution, it has no knowledge of such things.

Second, the compiler phases defined in section 5.1.1.2 don't allow evaluation of _Generic keywords before string concatenation. The relevant phases, quoted from the spec, are

  1. Adjacent string literal tokens are concatenated.

  2. White-space characters separating tokens are no longer significant. Each preprocessing token is converted into a token. The resulting tokens are syntactically and semantically analyzed and translated as a translation unit.

The _Generic keyword must be evaluated in phase 7, since it requires knowledge that is only available after tokens have been syntactically and semantically analyzed, e.g. that abc is a structure with member a. Hence, multiple _Generic keywords cannot take advantage of string concatenation to produce a single string literal.

user3386109
  • 34,287
  • 7
  • 49
  • 68
  • 1
    I'll accept this answer as the correct one, as it contains a nice explanation why my original idea wouldn't work. However, I couldn't resist inventing a horrible hack just for the sake of it :) – Lundin Dec 21 '15 at 13:42
2

Nice question, you can paste a string passing another parameter:

#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;
}
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • But what if I want 3 or more strings? Then it just keeps piling up nested macro calls. – Lundin Dec 21 '15 at 09:52
  • Actually, it doesn't work if combined with a second _Generic statement. Consider `printf(CS2(abc.a, CS2(abc.b, "hello")), abc.a);`. Still the same problem as I ran into. – Lundin Dec 21 '15 at 10:34
1

Just for the record, it turns out it is possible to generate a string constant based on _Generic at compile-time, by using other dirty tricks than those available from the pre-processor.

The solution I came up with is so ugly that I barely dare to post it, but I'll do so just to prove it possible.

Don't write code like this!

#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;
}

Output:

1 a
1 2
a 2

Explanation:

The macro print2 is a wrapper around printf and prints exactly 2 arguments, no matter type, with their correct conversion specifiers.

It builds up a string based on a struct, to which the conversion specifier string literals are passed. Each array place-holder for such a conversion specifier was purposely declared too small to fit the null termination.

Finally, this struct is dumped into a union which can interpret the whole struct as a single string. Of course this is quite questionable practice (even though it doesn't violate strict aliasing): if there is any padding then the program will fail.

Lundin
  • 195,001
  • 40
  • 254
  • 396