0

I try to write a wrapper for printf in C++. Of course i found va_list but dont know how its applicable to me because the wrapper will not be called directly. I will show later on.

I parse a script that includes a function with an unknown number of arguments like

ASTNode node(Token(PRINT, PRINT));
consume(PRINT);
consume(LPAREN);
node.make_child(variable()); // <-- formatstring (node.child[int])
while(current_token._type() != RPAREN) {
    consume(COMMA);
    node.make_child(variable()); // <-- the values to replace in formatstring (node.child[int++])
    i++; 
}
consume(RPAREN);
return node;

The first will be the formatstring and the others will be the values to replace in the format string so the function where i execute it will look like

if(node._token()._type() == PRINT) {
    Token formatstring = visit(*node.child[0]);
    Token temp;
    int i = 1;
    while(i < node.child.size()) {
        visit(*node.child[i++]); // <-- the values to replace in formatstring        
    }        
}

and doesnt take any "real" parameter. How can i build a dynamic parameter array using va_list or another method?

Thank you

Edit It seems my question is unclear to someone..

printf is called like printf(formatstring, param1, param2, param3...) and i want to do build the parameters passed after the first parameter (formatstring) in a loop like

// Pseudocode
out("printf(");
out($myformatstring);
int i = 1;
while(i<parameter_count) {
    out(parameter[i++]);
    out(",");
}
out(")");
  • Looks to me like [boost::format](https://www.boost.org/doc/libs/1_66_0/libs/format/doc/format.html) would be better for your. – NathanOliver Jan 25 '19 at 13:42
  • @Jabberwocky it should be clear now. if not, never mind.. Nathan i cant install boost – StrangerThings Jan 25 '19 at 13:50
  • why do you not use _iostream_ ? `os << x;` rather than `out(x);` – bruno Jan 25 '19 at 13:50
  • 1
    @bruno iostreams are an abomination and overloading the bit-shift operators was a bad idea. – melpomene Jan 25 '19 at 13:51
  • @bruno i stated that its pseudocode because Jabberwocky hasnt understood my question – StrangerThings Jan 25 '19 at 13:53
  • @StrangerThings it is not pseudo code, and you can do in sequence like `io << 12 << f("foo") << " marvelous" << ...` without having to write `io << 12; io << f("foo); io << " marvelous"; ...` and that work in the other direction to read rather than write. – bruno Jan 25 '19 at 13:54
  • @bruno You're missing the point. None of that has anything to do with the question. It's pseudocode. – melpomene Jan 25 '19 at 13:56
  • @bruno so i can shift the String to os like `os << "printf("string", ...)"` and it will replace something in my string? I dont understand how it should work. Can you explain it in an answer? – StrangerThings Jan 25 '19 at 13:58
  • 2
    Possible duplicate of [How can I call (not define) a function with a variable number of arguments in C?](https://stackoverflow.com/questions/3321063/how-can-i-call-not-define-a-function-with-a-variable-number-of-arguments-in-c) – melpomene Jan 25 '19 at 14:00
  • @StrangerThings it seems there is a quiproquo and I don't understand your goal – bruno Jan 25 '19 at 14:00
  • @StrangerThings IO streams do not replace the C format specifiers. You'd have to find them yourself, write until you find one, write the argument, continue writing format string after the format specifier. You might, though, introduce your own place holder sequence to make the stuff easier for you (*if* you feel this approach is suitable for you). – Aconcagua Jan 25 '19 at 14:01
  • @melpomene my function handler doesnt take any arguments because its already inside a switch:case statement so using `argc, argv` isnt possible.. – StrangerThings Jan 25 '19 at 14:03
  • Oh, sry i was too fast.. Of course it could be a solution.. I just thought there is an easier way.. – StrangerThings Jan 25 '19 at 14:04
  • 1
    Possible duplicate of ["Unpack" an array to call a function with variadic template](https://stackoverflow.com/questions/34929856/unpack-an-array-to-call-a-function-with-variadic-template) – Sorin Jan 25 '19 at 14:04
  • @Sorin `printf` is not a variadic template. – melpomene Jan 25 '19 at 14:05
  • @StrangerThings There is no easy way, and I recommend against letting scripts call `printf` with arbitrary arguments anyway. That opens up security holes (`printf` can dereference pointers and even write to memory). – melpomene Jan 25 '19 at 14:06
  • @Aconcagua seems like i have to.. Thanks to all – StrangerThings Jan 25 '19 at 14:07
  • its not only printf, any function could be called without or with any amount of args because i parse and interpret it at runtime. Just imagine a scripting language where you have to define the number of arguments everytime before writing a function. Of course i check the parameter Tokens while parsing. – StrangerThings Jan 25 '19 at 14:09
  • `printf(3)` **IS NOT A C++ FUNCTION**. It is there for compatibility reasons, but it is deprecated to use it. – Luis Colorado Jan 26 '19 at 08:24

3 Answers3

1

As I understand you have a given format string, and "read/parse" arguments.

You have so 2 problems to handle, handling the format and using correct type for argument.

printf family doesn't support partial replacement (contrary to Qt for example, which allow QString("%1 %2").arg("Hello") resulting into QString("Hello %2") which allow chaining).

So you have to parse the full format string manually:

  • print regular characters.
  • when % is encountered, retrieve the flag format (unless it is %%, in that case display % directly).

  • from flag format, switch to appropriate conversion, something like

     // flagFormat would "simply" be %i, %010d, %4.2f or %+.0e
     switch (format_type) {
         case EFormatType::String: // %s
              printf(flagFormat, to_string(args[i]).c_str()); break;
         case EFormatType::Int: // %i, %d
              printf(flagFormat, to_int(args[i])); break;
         case EFormatType::String: // %f, %F, %e, %g, %
              printf(flagFormat, to_double(args[i])); break;
         // ...
     }
     ++i;
    
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0
// Pseudocode
// I would like to propose a way to solve the problem, added some stuff here

out("printf("); 

// you should create format string in accordance with types of parameters
$myformatstring = ""   // let suppose  myformatstring is of type "string"
                   // and it has defined operator "+=" (concatenation of strings)
for(int j = 0;j < parametar_count; j++)
{
    switch(parametar[j].type )  // suppose that you have type information of param's 
    {
        case 'i': myformatstring += " %d "; break;
        case 'f': myformatstring += " %f "; break;
        case 's': myformatstring += " %s"; break;
    ...  // you should handle all types you use ...

    }
}
$myformatstring += "\\n\","; // terminate format string and write a comma before 
                             // other arguments...
out($myformatstring);

int i = 1;
while(i<parameter_count) {
   out(parameter[i++]);
   if( i < parameter_count -1 )   // you don't need comma after last parameter 
      out(",");
}
out(");");
risbo
  • 188
  • 7
  • Please, add explanations of your changes – Dmytro Dadyka Jan 25 '19 at 15:31
  • 1
    This will not execute the replacement, it just prints out a string representing the function. on the screen you will see `printf("Hello %d world %s", param1, param2,...)` im looking for something like `call_user_func_array` in php – StrangerThings Jan 25 '19 at 17:15
0
/* 
  there is example that explains how to use stdarg for a function 
  with variable number of parameters
*/ 



#include <stdio.h>
#include <stdarg.h>

void myfunc(char* fmt, ...);

/* this example folows logic of printf to specify format
    %d - integer
    %c - single character
    %s - string
    other character will be copied to output
*/

int main( int argc, char* argv[])
{
    myfunc("%s %d ABCD %c", "a string", 4, 'D'); 
}


void myfunc(char *fmt, ...)
{
va_list ap;
int d;
char c, *s;

  va_start(ap, fmt);
  while (*fmt)
  {
    if(*fmt == '%')
    { 
       fmt++;
       switch (*fmt)     // detect format characters
       {
         case 's':              /* string format character is recognized*/
             s = va_arg(ap, char *);
             printf("string: %s\n", s);  /* this example just print out paramater, you can do what you want */
         break;

         case 'd':              /* integer format character */
             d = va_arg(ap, int);
             printf("int: %d\n", d);
         break;

         case 'c':              /* char format character*/
            c = (char) va_arg(ap, int);
            printf("char: %c\n", c);
         break;

     default:
       printf("Just copy non format characters %c\n",*fmt);  // copy non format characters after %
       } 
     }
     else
       printf("just copy '%c'\n", *fmt); 

    fmt++;
  }
  va_end(ap);
}
risbo
  • 188
  • 7