I am writing a function that saves data to a file in C, making use of the fprintf function. However, this requires a placeholder as one of its arguments. For example: %s for strings, %d for integers... What should I do if the data type is generic, that is, the programmer using this function can set the data type of the variable I am writing to the file to anything he wants? Thanks!
-
1Post what you have written in a "function that saves data to a file" – chux - Reinstate Monica Jan 01 '14 at 17:40
-
formatted print (printf) is useful since it "converts" to a string a datum (it "gets" its string representation). But it can't know what kind of datum it is, so it is up to you to specify it. "You" here can mean also simply the "caller" to your function. – ShinTakezou Jan 01 '14 at 17:41
5 Answers
Since the type of the data is known only at run-time, you will have to:
generate the format string used by the fprintf function at run-time, or
choose between multiple harcoded format strings to pass to fprintf.
The choice between these two options could depend on how many different types you have to deal with, and/or how you know at run-time the type of your placeholder argument.

- 4,250
- 1
- 16
- 25
you can open your file with the w+
inorder to write your data in binary format
Then you can use fwrite()
to write data directly in binary form, intended to be read by fread()
later.

- 41,599
- 58
- 163
- 268
-
1though, I suspect he wants a "string representation" of the data, not the "raw" data. – ShinTakezou Jan 01 '14 at 17:42
-
I'm trying out this statement but the program crashes: fwrite((scan->Item.data), sizeof(Item),1, filepointer); Where Item is a struct containing a void pointer named data and scan is a pointer to a node (i'm using a linked list) – user3126802 Jan 01 '14 at 18:14
-
-
@user3126802 share your code problem in new topic inorder to get help on this crash – MOHAMED Jan 01 '14 at 18:46
C not supported generic except in void* I think , I've been working on a small project that make C a little bit Generic .. you can do what you want by set one of the arguments for the type say 0 for string and 1 for integers
then make an if-else statement then use printf for each one alone .
int print (FILE f,void*x,int type) { if(type==0) return fprintf(f,"%s",(char*)x); else return fprintf(f,"%d",*(int*)x); }

- 157
- 1
- 15
-
the idea is correct (the caller has to specify the type of the datum, so that it can pick the right way to represent it as string), but the suggested implementation (if-else) is not as good, according to me. – ShinTakezou Jan 01 '14 at 17:44
-
I've edit and add a small example , you can convert it to switch statement with good Generic sense ;) – Mostafa 36a2 Jan 01 '14 at 17:48
-
1use of symbolic names instead of numbers (0 is string, 0 is integer, how I can remember that?) is preferred... then, the example for the `int` type looks wrong, %d expects the value, not the pointer to! – ShinTakezou Jan 01 '14 at 17:56
Can try something like following :
Use a struct
to know type of data while writing
typedef enum { String, Int, Char} datatype;
struct record{
datatype dt;
void* data;
};
int myfprintf(FILE* stream, struct record* rec){
switch(rec->dt){
case Char: return fprintf(stream, "%c", *((char*)(rec->data)));
case Int: return fprintf(stream, "%d", *((int*)rec->data));
case String: return fprintf(stream, "%s", (char*)rec->data);
//....
default: return fprintf(stream,"%s", "Error");
}
}
Then use myprintf
passing the stream and the data of type record
Alternatively you can use a union
too in similar fashion
With C11
you can do following :- (taken from here ):
#define printf_dec_format(x) _Generic((x), \
char: "%c", \
signed char: "%hhd", \
unsigned char: "%hhu", \
signed short: "%hd", \
unsigned short: "%hu", \
signed int: "%d", \
unsigned int: "%u", \
long int: "%ld", \
unsigned long int: "%lu", \
long long int: "%lld", \
unsigned long long int: "%llu", \
float: "%f", \
double: "%f", \
long double: "%Lf", \
char *: "%s", \
void *: "%p")
#define fprint(stream, x) fprintf(stream,printf_dec_format(x), x)
fprint(stdin,(char *)"P0W"); // for string literal

- 46,614
- 9
- 72
- 119
C does not have a generic data type, so one must be created.
typedef enum {
Generic_int, Generic_float, Generic_double, Generic_string
} Generic_Type;
typedef struct {
Generic_Type type;
union {
int i;
float f;
double d;
char *s;
// other types
} u;
} Generic;
void Generic_printf(Generic *G) {
switch (G->type) {
case Generic_int:
printf("%d", G->u.i);
break;
case Generic_float:
printf("%.*e", FLT_DECIMAL_DIG, G->u.f);
break;
case Generic_double:
printf("%.*e", DBL_DECIMAL_DIG, G->u.d);
break;
case Generic_string:
printf("\"%s\"", G->u.s); // Enclose string in quotes
break;
// other
}
}

- 143,097
- 13
- 135
- 256
-
-
1@ShinTakezou Interesting though, to consider C11 as its now 2014. – chux - Reinstate Monica Jan 01 '14 at 18:01
-
@self The `_Generic` is interesting, but typically the destination also needs a hint to the type for later reading. Something like `""` around strings, an exponent in FP types. Also `float`, `double` is see should be written to different precision. This all leads to a less generic `printf()` as the number of arguments may differ. I do see how `_Generic` still could be used and thanks for pointing it out. – chux - Reinstate Monica Jan 01 '14 at 18:10