Reputation: 1230
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
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
Reputation: 134008
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