user66820: I have a function that concatenates a print line, however I am having trouble getting some of the strings to format correctly.
Using Serial.print formats the output from keypad.getKey() correctly, however when I try to output the same format using printConcatLine() it does not give me the same output. I've tried casting it as both a char and an int...

void printConcatLine(const char* mask, ...) {
    va_list params;
    va_start(params, mask);

    while(*mask != '\0') {
      if (*mask == 'i') {
          Serial.print(va_arg(params, int));
      } else if(*mask == 'c') {
          Serial.print(va_arg(params, const char *));
      } else if (*mask == 'f' || *mask == 'd') {
          Serial.print(va_arg(params, double));
      } 
      ++mask;    
    }

    va_end(params);
    Serial.println();
}

char btnPressed = keypad.getKey();
  if (btnPressed) {
    Serial.print("BtnPressed: ");
    Serial.println(btnPressed);
    // Output: "BtnPressed: 1"

    printConcatLine("ci", "BtnPressed: ", btnPressed); 
    // Output: "BtnPressed: 49"

    printConcatLine("cc", "BtnPressed: ", btnPressed); 
    // Output: "BtnPressed: "  
  }

I also tried changing:

va_arg(params, const char *)

to 

va_arg(params, char)

which also did nothing.
How can I modify the call to va_arg() to properly output the same output as Serial.print()?

user7508: With the format "ci", the argument is interpreted as an integer, and
the output is correct: 49 is the ASCII code of '1'.
With the format "cc", the argument is interpreted as a string (pointer to an
array of chars), which is incorrect.
One (bad) solution is to use the "cc" format and pass &btnPressed as
an argument. The problem with this approach is that the byte in memory
right after btnPressed may not be a zero, and the format expects a
NUL-terminated string.
A better approach would be to have one format for a char and a
different one for a string. I suggest "c" and "s" respectively. But
then you should be aware that there are special promotion rules
associated with variadic functions. When playing with your code, the
compiler reminded me of this one:

warning: ‘char’ is promoted to ‘int’ when passed
  through ‘...’

So the function should expect an int, and cast it to char in order
to call the correct overload of Serial.print():
void printConcatLine(const char* mask, ...) {
    va_list params;
    va_start(params, mask);

    while(*mask != '\0') {
      if (*mask == 'i') {
          Serial.print(va_arg(params, int));
      } else if(*mask == 's') {
          Serial.print(va_arg(params, const char *));
      } else if(*mask == 'c') {
          Serial.print((char) va_arg(params, int));
      } else if (*mask == 'f' || *mask == 'd') {
          Serial.print(va_arg(params, double));
      } 
      ++mask;    
    }

    va_end(params);
    Serial.println();
}

Used like this:
printConcatLine("sc", "BtnPressed: ", btnPressed); 
// Output: "BtnPressed: 1"


user4143: Edgar beat me to it by a few seconds, but my answer is pretty much the same as his, so read that first.  
I will however add a few extra notes and pointers:

It's better to assign the va_args to an actual variable. That makes it both easier to read and understand what's going on, and more certain for the overloading of the print function to know what is supposed to happen (at least from the reader's perspective) that mere casting.
Bytes and chars are promoted to integer when being passed to a variadic function, so you have to treat them as such
float and double happen to be the same on the AVR based Arduinos, so your code will be OK there, but the same can't be said for other chips. If you want your code to be portable then you should split float and double into separate handlers since they use different sizes.
It can be good to "pass through" unknown characters in the format string so that you can add extra formatting in there. See my example below.

Here's my variant of the same code. Note the colon and space in the format that is used to add more formatting to the output (this could be enhanced by adding a % before any format characters as printf does). I also like to use a separate pointer to iterate over the format string instead so that the original start pointer is still available if I should ever want it.
Also note the difference between c and i: use c to print a letter from a char, and i to print the number in a char instead.
void printConcatLine(const char *mask, ...) {
    va_list params;
    va_start(params, mask);

    char *ptr = (char *)mask;
    while (*ptr != '\0') {
        if (*ptr == 'c') {
            int c = va_arg(params, int);
            Serial.write(c);
        } else if (*ptr == 'i') {
            int i = va_arg(params, int);
            Serial.print(i);
        } else if (*ptr == 's') {
            const char *s = va_arg(params, const char *);
            Serial.print(s);
        } else if (*ptr == 'f' || *ptr == 'd') {
            // Be careful with this. It's not portable. On AVR float
            // and double are the same, but that's not the case on other
            // microcontrollers. It would be better to split them.
            double d = va_arg(params, double);
            Serial.print(d);
        } else {
            Serial.write(*ptr);
        }
        ptr++;
    }

    va_end(params);
    Serial.println();
}

void setup() {
    Serial.begin(115200);
    char btnPressed = 1;
    float flt = 3.141592653;

    if (btnPressed) {
        printConcatLine("c: sisd", 'Q', "BtnPressed: ", btnPressed, " done ", flt);
    }
}

void loop() {
}


user37622: I recommend to use printf if you don't need float support in printf. This code produces a little smaller compiled code then the one in Majenko's answer and has the full power of the printf except of float.
#include <StreamLib.h>

void setup() {
    Serial.begin(115200);
    char btnPressed = 1;
    float flt = 3.141592653;

    byte b;
    BufferedPrint bp(Serial, &b, 1);

    if (btnPressed) {
        bp.printf("%c: %s %i %s", 'Q', "BtnPressed: ", btnPressed, " done ");
        bp.println(flt, 4);
    }
}

void loop() {
}

With StreamLib you can of course print float with normal single print function for floats, the same as for Serial or network Client, with second parameter for the number of decimal places.
If you want to create a formatted C-string you can use the CStringBuilder of the StreamLib. It too builds the string with print functions and has 'printf' too.

66820: <p>I have a function that concatenates a print line, however I am having trouble getting some of the strings to format correctly.</p>

<p>Using Serial.print formats the output from <strong>keypad.getKey()</strong> correctly, however when I try to output the same format using <strong>printConcatLine()</strong> it does not give me the same output. I've tried casting it as both a char and an int...
</p>

<pre class="lang-cpp prettyprint-override"><code>void printConcatLine(const char* mask, ...) {
    va_list params;
    va_start(params, mask);

    while(*mask != '\0') {
      if (*mask == 'i') {
          Serial.print(va_arg(params, int));
      } else if(*mask == 'c') {
          Serial.print(va_arg(params, const char *));
      } else if (*mask == 'f' || *mask == 'd') {
          Serial.print(va_arg(params, double));
      } 
      ++mask;    
    }

    va_end(params);
    Serial.println();
}

char btnPressed = keypad.getKey();
  if (btnPressed) {
    Serial.print("BtnPressed: ");
    Serial.println(btnPressed);
    // Output: "BtnPressed: 1"

    printConcatLine("ci", "BtnPressed: ", btnPressed); 
    // Output: "BtnPressed: 49"

    printConcatLine("cc", "BtnPressed: ", btnPressed); 
    // Output: "BtnPressed: "  
  }
</code></pre>

<p>I also tried changing:</p>

<blockquote>
  <p>va_arg(params, const char *)</p>
</blockquote>

<p>to </p>

<blockquote>
  <p>va_arg(params, char)</p>
</blockquote>

<p>which also did nothing.</p>

<p>How can I modify the call to <strong>va_arg()</strong> to properly output the same output as <strong>Serial.print()</strong>?</p>
 7508: <p>With the format <code>"ci"</code>, the argument is interpreted as an integer, and
the output is correct: 49 is the ASCII code of <code>'1'</code>.</p>

<p>With the format <code>"cc"</code>, the argument is interpreted as a string (pointer to an
array of chars), which is incorrect.</p>

<p>One (bad) solution is to use the <code>"cc"</code> format and pass <code>&amp;btnPressed</code> as
an argument. The problem with this approach is that the byte in memory
right after <code>btnPressed</code> may not be a zero, and the format expects a
NUL-terminated string.</p>

<p>A better approach would be to have one format for a <code>char</code> and a
different one for a string. I suggest <code>"c"</code> and <code>"s"</code> respectively. But
then you should be aware that there are special promotion rules
associated with variadic functions. When playing with your code, the
compiler reminded me of this one:</p>

<blockquote>
  <p>warning: <strong>‘char’</strong> is promoted to <strong>‘int’</strong> when passed
  through <strong>‘...’</strong></p>
</blockquote>

<p>So the function should expect an <code>int</code>, and cast it to <code>char</code> in order
to call the correct overload of <code>Serial.print()</code>:</p>

<pre class="lang-cpp prettyprint-override"><code>void printConcatLine(const char* mask, ...) {
    va_list params;
    va_start(params, mask);

    while(*mask != '\0') {
      if (*mask == 'i') {
          Serial.print(va_arg(params, int));
      } else if(*mask == 's') {
          Serial.print(va_arg(params, const char *));
      } else if(*mask == 'c') {
          Serial.print((char) va_arg(params, int));
      } else if (*mask == 'f' || *mask == 'd') {
          Serial.print(va_arg(params, double));
      } 
      ++mask;    
    }

    va_end(params);
    Serial.println();
}
</code></pre>

<p>Used like this:</p>

<pre class="lang-cpp prettyprint-override"><code>printConcatLine("sc", "BtnPressed: ", btnPressed); 
// Output: "BtnPressed: 1"
</code></pre>
 4143: <p>Edgar beat me to it by a few seconds, but my answer is pretty much the same as his, so read that first.  </p>

<p>I will however add a few extra notes and pointers:</p>

<ul>
<li>It's better to assign the va_args to an actual variable. That makes it both easier to read and understand what's going on, and more certain for the overloading of the print function to know what is supposed to happen (at least from the reader's perspective) that mere casting.</li>
<li>Bytes and chars are promoted to integer when being passed to a variadic function, so you have to treat them as such</li>
<li><code>float</code> and <code>double</code> happen to be the same on the AVR based Arduinos, so your code will be OK there, but the same can't be said for other chips. If you want your code to be portable then you should split <code>float</code> and <code>double</code> into separate handlers since they use different sizes.</li>
<li>It can be good to "pass through" unknown characters in the format string so that you can add extra formatting in there. See my example below.</li>
</ul>

<p>Here's my variant of the same code. Note the colon and space in the format that is used to add more formatting to the output (this could be enhanced by adding a <code>%</code> before any format characters as <code>printf</code> does). I also like to use a separate pointer to iterate over the format string instead so that the original start pointer is still available if I should ever want it.</p>

<p>Also note the difference between <code>c</code> and <code>i</code>: use <code>c</code> to print a letter from a char, and <code>i</code> to print the number in a char instead.</p>

<pre><code>void printConcatLine(const char *mask, ...) {
    va_list params;
    va_start(params, mask);

    char *ptr = (char *)mask;
    while (*ptr != '\0') {
        if (*ptr == 'c') {
            int c = va_arg(params, int);
            Serial.write(c);
        } else if (*ptr == 'i') {
            int i = va_arg(params, int);
            Serial.print(i);
        } else if (*ptr == 's') {
            const char *s = va_arg(params, const char *);
            Serial.print(s);
        } else if (*ptr == 'f' || *ptr == 'd') {
            // Be careful with this. It's not portable. On AVR float
            // and double are the same, but that's not the case on other
            // microcontrollers. It would be better to split them.
            double d = va_arg(params, double);
            Serial.print(d);
        } else {
            Serial.write(*ptr);
        }
        ptr++;
    }

    va_end(params);
    Serial.println();
}

void setup() {
    Serial.begin(115200);
    char btnPressed = 1;
    float flt = 3.141592653;

    if (btnPressed) {
        printConcatLine("c: sisd", 'Q', "BtnPressed: ", btnPressed, " done ", flt);
    }
}

void loop() {
}
</code></pre>
 37622: <p>I recommend to use printf if you don't need float support in printf. This code produces a little smaller compiled code then the one in Majenko's answer and has the full power of the printf except of float.</p>

<pre><code>#include &lt;StreamLib.h&gt;

void setup() {
    Serial.begin(115200);
    char btnPressed = 1;
    float flt = 3.141592653;

    byte b;
    BufferedPrint bp(Serial, &amp;b, 1);

    if (btnPressed) {
        bp.printf("%c: %s %i %s", 'Q', "BtnPressed: ", btnPressed, " done ");
        bp.println(flt, 4);
    }
}

void loop() {
}
</code></pre>

<p>With StreamLib you can of course print float with normal single print function for floats, the same as for Serial or network Client, with second parameter for the number of decimal places.</p>

<p>If you want to create a formatted C-string you can use the CStringBuilder of the StreamLib. It too builds the string with <code>print</code> functions and has 'printf' too.</p>
