Motla
Motla

Reputation: 1230

Use printf for ternary operator arguments with mixed types

I have a tricky, but interesting question. Hang in there!

Let's assume I write embedded code, and I am in a system where I have to store values in 16bit unsigned to be compatible with other systems. Some values have a decimal part, but luckily I know the position of the separator in advance (fixed point).

Now let's consider this structure:

typedef struct{

   // pointer to a function that computes the "real" value
   float (*getRealValue)(void);

   // pointer to a function that computes a "stringified" value
   bool (*getCustomString)(char *);

} var_info_t;

And let's declare one variable var1, which is a temperature:

uint16_t var1 = 1530; // means 15.3°C

float var1_toFloat(void){ return (float)var1/100; } // should return 15.3

var_info_t var1_info = { .getRealValue = var1_toFloat, .getCustomString = NULL };

and another variable var2, which happens to be more like a boolean:

uint16_t var2 = 1; // means Enabled

bool var2_toString(char* buffer){  // should write "Enabled"
   if(buffer) sprintf(buffer, "%s", var2 ? "Enabled" : "Disabled");
   return true;
}
var_info_t var2_info = { .getRealValue = NULL, .getCustomString = var2_toString };

Now I want to display these values together on a screen, with some fancy formatting that can change depending of where these value are written on the screen.

(I just have to call TEXT_SetText(int widget_id, char* text) to update my GUI widgets.)

What I want to accomplish is to create a "wrapper" to TEXT_SetText() like this...

static char text[128], buf[2][32];

#define GET_VAR_AUTO(var_info, i) \
  ((var_info.getCustomString != NULL && var_info.getCustomString(buf[i])) ? buf[i] : \
   (var_info.getRealValue != NULL ? var_info.getRealValue() : 0))

void my_TEXT_SetText(int widget_id, char* format, var_info_t a, var_info_t b){
   snprintf(text, sizeof(text), format, GET_VAR_AUTO(a, 0), GET_VAR_AUTO(b, 1));
   TEXT_SetText(widget_id, text);
}

...so that calling my_TEXT_SetText(0, "Regulator is %s at %.1f\260C", var2_info, var1_info);

will display a nice Regulator is Enabled at 15.3°C inside my box.

The real advantage here is that you can update the text in real-time just by calling this same function periodically from "anywhere" (without "knowing anything" about the variables or their values). Also you can simply expand the number of variables inside the same text by increasing the number of snprintf arguments.

The problem: the GET_VAR_AUTO macro is not syntactically correct because it mixes char* from buf[i] and float from getRealValue() in the possible results of the ternary operator:

error: type mismatch in conditional expression

BUT, knowing that sprintf is a variadic function, that treats its arguments according to the types given in the format string (%f, %s, ... thanks to the va_arg() function), is there a way to find a common abstract type for the ternary operator which would be accepted by the sprintf ?

I tried a lot of things but I can't get the char* and float to work simultaneously.

(Unfortunately I'm using C, not C++)

Upvotes: 2

Views: 790

Answers (2)

alk
alk

Reputation: 70971

Define

#define FLOAT_STR_SIZE_MAX (32) /* or what ever you feel suits */
#define FTOA(b, s, x) \
  (snprintf(b, s, "%f", x), b)

and replace

 (var_info.getRealValue != NULL ? var_info.getRealValue() : 0)

by

 FTOA((char[FLOAT_STR_SIZE_MAX]){'\0'},
   FLOAT_STR_SIZE_MAX,
   (var_info.getRealValue != NULL ? var_info.getRealValue() : 0.))

:-)

Upvotes: 2

No, there simply isn't any generic type, and it cannot be done in C except in very special cases.

For example floating point arguments are passed in floating point/SSE registers on x86-64 for example, whereas integer arguments in integer registers. There is no way of passing a variable argument that is passed as "both" in variable arguments, because in a function call like

printf("%f %d",  f,  d  );
       R1        F1  R2

The arguments would be passed in two general-purpose registers and one floating point register like so, whereas in

printf("%d %f",  d,  f  );
       R1        R2  F1

they would be passed in the same registers again!

Indeed,

#include <stdio.h>
int main(void) {
    printf("%f %s\n", "hello world", 2.5);
}

compiled with GCC for x86-64/Linux, and run, will not print any random garbage but exactly 2.50000 hello world. So on x86-64 the most you could do is to pass the last argument in both a floating point and a general purpose register.


What you could do is to write your own printf-like function that would accept pointers to void for the arguments, then dereference them based on the actual type.

It will still be quite tricky.

If you're really desperate, consider trying libffi.

Upvotes: 4

Related Questions