How to make this ellipsis overload non-ambiguous

I use the p_original() -function below without problems. My code has a hundred of these calls.

Now I want to overload it to be able to append text in several steps to a character array before I print it. I know that this is easily done using String(), but this way I hope to avoid fragmenting my free ram.

All I want to achieve is to be able to supply a bool to the p() function so it can decide if to append or start from the first character.

//Original, works fine
char *p_original( const char *fmt, ... ) {
  static char tmp[ 192 ]; // resulting string limited to 192 chars
  va_list args;
  va_start ( args, fmt );
  vsnprintf(tmp, sizeof( tmp ), fmt, args );
  va_end ( args );
  return tmp;
}


// My intended solution with a possibility to append text
char *p( const char *fmt, ... ) { 
  return p ( 0, fmt, __VA_ARGS__ );
}
char *p_append( const char *fmt, ... ) {
  return p ( 1, fmt, __VA_ARGS__ );
}

char *p( bool append, const char *fmt, ... ) {
  static char tmp[ 224 ]; // resulting string limited to 224 chars
  va_list args;
  va_start ( args, fmt );
  if ( append ) {
    vsnprintf( tmp[ strlen(tmp) ], sizeof( tmp ), fmt, args );
  }
  else {
    vsnprintf(tmp, sizeof( tmp ), fmt, args );
  }
  va_end ( args );
  return tmp;
}

Below is a small part of the error-message. Many warnings and errors on ambiguity.

How do I pass this bool without ambiguity?

D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\pushover.ino: In function 'int8_t securePublishPUSHOVER(int8_t, const char*, const char*, int8_t, int8_t)':
D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\pushover.ino:158:73: warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second: [enabled by default]
       log( 0, p( G( "securePublishPUSHOVER() using file %s" ), freeFile ) );
                                                                         ^
D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\filelogstring.ino:307:7: note: candidate 1: char* p(bool, const char*, ...)
 char *p( bool append, const char *fmt, ... ) {
       ^
D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\filelogstring.ino:300:7: note: candidate 2: char* p(const char*, ...)
 char *p( const char *fmt, ... ) {
       ^
D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\pushover.ino:172:81: warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second: [enabled by default]
         log(1, p( G( "securePublishPUSHOVER() failed using file %s" ), freeFile ) );
                                                                                 ^
D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\filelogstring.ino:307:7: note: candidate 1: char* p(bool, const char*, ...)
 char *p( bool append, const char *fmt, ... ) {
       ^
D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\filelogstring.ino:300:7: note: candidate 2: char* p(const char*, ...)
 char *p( const char *fmt, ... ) {
       ^
D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\rullgardin.ino: In function 'int MotorControl(MotorCommand, int8_t)':
rullgardin:142:148: error: call of overloaded 'p(const char [52], const char [15], const char [15], const char [12], int8_t&)' is ambiguous
       log(1, p("motorState %s -> %s using command %s where pos = %d", g_motorStates[oldState], g_motorStates[g_motorState], g_motorCommands[c], pos) );
                                                                                                                                                    ^

Maybe, instead of integer constants 0 and 1, it would work if you used boolean values.

// My intended solution with a possibility to append text
char *p( const char *fmt, ... ) {
  return p ( false, fmt, __VA_ARGS__ );
}
char *p_append( const char *fmt, ... ) {
  return p ( true, fmt, __VA_ARGS__ );
}

Perhaps an explicit cast would be required:

// My intended solution with a possibility to append text
char *p( const char *fmt, ... ) {
  return p ( (boolean) false, fmt, __VA_ARGS__ );
}
char *p_append( const char *fmt, ... ) {
  return p ( (boolean) true, fmt, __VA_ARGS__ );
}

Thanks for looking at my problem!

I tried your ideas, but no success. Then I tried to change the name of the called function to avoid the ambiguity:

char *p( const char *fmt, ... ) {
  return _p ( (boolean) false, fmt, __VA_ARGS__ );
}
char *p_append( const char *fmt, ... ) {
  return _p ( (boolean) true, fmt, __VA_ARGS__ );
}

char *_p( bool append, const char *fmt, ... ) {

This revealed the real problem:

D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\filelogstring.ino: In function 'char* p(const char*, ...)':
filelogstring:300:37: error: '__VA_ARGS__' was not declared in this scope
   return _p ( (boolean) false, fmt, __VA_ARGS__ );

And if I had looked from the beginning of all errors, I would have found:

D:\Mina Dokument\Arduino\MQTT\mqtt_RullgardinVardagsrum_v004\filelogstring.ino:300:37: warning: __VA_ARGS__ can only appear in the expansion of a C99 variadic macro [enabled by default]
   return _p ( (boolean) false, fmt, __VA_ARGS__ );

Does that mean I need to rewrite them as macros?

I think you are supposed to use the va_start() macro to generate a va_list. You can then pass the vs_list to another function for processing.
https://en.cppreference.com/w/cpp/utility/variadic

char *p( const char *fmt, ... )
{
  va_list args;
  va_start(args, fmt);
  char *result = _p (false, fmt, args);
  va_end(args);
  return result;
}


char *p_append( const char *fmt, ... )
{
  va_list args;
  va_start(args, fmt);
  char *result = _p (true, fmt, args);
  va_end(args);
  return result;
}


char *_p( bool append, const char *fmt, va_list args)
{
  // Use va_arg(args, type) to access each arg
}

va_start and va_args are really, conceptually, quite simple. But, the implementation varies from compiler to compiler.

va_start gets two arguments, a va_list structure, and a pointer to the last non-variadic argument in the argument list, which is, in reality, a pointer to the functions stack frame. This lets va_args locate the variadic arguments, because they begin immediately below the passed pointer. va_args is used to get the value of the next variadic argument. It is passed a pointer to the variable to receive the value, and the type of the value to be fetched. So, va_args simply walks the stack frame pulling off arguments of the specified types as requested. Of course, it is important that the type specified in the request matches the type actually placed in the stack frame, or that value, and likely all subsequent ones, will be incorrect.

Here is a very clear explanation of how to use them:

Regards,
Ray L.

If you're curious HOW variadic variables actual work, here are typical implementations of the macros for va_start and va_arg:

typedef unsigned char *va_list;

#define va_start(list, param) (list = (((va_list)&param) + sizeof(param)))

#define va_arg(list, type)    (*(type *)((list += sizeof(type)) - sizeof(type)))

Thank you John, I am very happy to say that it works! I had to change the strlen()-parts. Below is the final code.

har *p( const char *fmt, ... )
{
  va_list args;
  va_start(args, fmt);
  char *result = _p (false, fmt, args);
  va_end(args);
  return result;
}

char *p_append( const char *fmt, ... )
{
  va_list args;
  va_start(args, fmt);
  char *result = _p (true, fmt, args);
  va_end(args);
  return result;
}

char *_p( bool append, const char *fmt, va_list args ) {
  static char tmp[ 224 ]; // resulting string limited to 224 chars
  if ( append ) {
    vsnprintf( tmp + strlen( tmp ), sizeof( tmp ) - strlen( tmp ), fmt, args );
  }
  else {
    vsnprintf(tmp, sizeof( tmp ), fmt, args );
  }
  return tmp;
}

Here is my test-code:

  else if ( topic == F( "t1" ) ) {
    p( "?first?" );
    p_append( "&middle&" );
    log( 9, p_append( "#last#" ) );
  }

  else if ( topic == F( "t2" ) ) {
    log( 9, p_append( "#just_adding_some_text#" ) );
  }

And the test-results are as I would expect them to be:

t1
2021-01-05 21:05:08 9 ?first?&middle&#last#
t1
2021-01-05 21:05:38 9 ?first?&middle&#last#
t2
2021-01-05 21:05:41 9 Received: 't2' - ''#just_adding_some_text#
t2
2021-01-05 21:05:50 9 Received: 't2' - ''#just_adding_some_text#

And a big thank you to Ray for the explanations. This is far far above me, but useful to everyone else reading this thread.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.