8

Recently I meet an annoying problem that I want to define functions like this:

std::string my_sprintf(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    ...
}
std::string my_sprintf(const std::string& format, ...)
{
    va_list args;
    va_start(args, format); // error
    ...
}

But it seems reference value can't be the last parameter when using variable length parameter list.

Is that a way that I can let user use std::string as format string?

Maybe factors or something else that detect std::string and convert it as c_str() would work, but I don't know how to deal with the following variable length parameter list.

Edit: I'm not using variadic template because i'm using vsprintf inside. Maybe avoid using vsprintf and using std::stringstream is an option?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Jim Yang
  • 479
  • 2
  • 12

2 Answers2

6

The behaviour is undefined: See C++ Standard 18.10/3 (for C++11 and C++14), or cppreference.com: Variadic Arguments.

Essentially you can only use types that are available in C with va_start, although an exception is made for the std::nullptr_t type.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 3
    They are also called **POD** *Plain Old Data*, [read this](http://stackoverflow.com/questions/146452/what-are-pod-types-in-c). – Iharob Al Asimi Jun 06 '16 at 07:32
  • I know excatly that this is forbidden, but I'm just asking is there a way to do so. – Jim Yang Jun 06 '16 at 07:37
  • Not with `va_start`, no. But there's no reason why you couldn't use variadic *templates*. Or just deep copy the string. The i/o stage of your function will be the bottleneck, not the deep copy. – Bathsheba Jun 06 '16 at 07:38
  • 1
    @Bathsheba Because i'm using vsprintf inside, it there a better way to avoid using it? – Jim Yang Jun 06 '16 at 07:41
  • You should indicate which C++ standard you are referring to: in the C++14 standard the information about `va_start()` is in 18.10/3 . – Michael Burr Jun 06 '16 at 07:42
2

In C++, the way to pass "unknown" arguments is to use variadic template:

template <typename ... Ts>
std::string my_sprintf(const std::string& format, Ts&&...args)
{
    return my_sprintf(format.c_str(), std::forward<Ts>(args)...);
}

and you version with const char* should probably also be variadic template, by using sprintf instead of vsprintf.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • @user2079303: With both version variadic, it works [Demo](http://coliru.stacked-crooked.com/a/649e95afbeee69b4). But indeed, with ellipsis, it is ambiguous and requires some renaming to workaround that. – Jarod42 Jun 06 '16 at 09:07
  • @Jarod42 oh, right. I tried to understand this code in a vacuum - without realizing that there is an overload. Sorry about the noise. – eerorika Jun 06 '16 at 09:13