Reputation: 175
I have modified the sample from https://en.cppreference.com/w/cpp/language/parameter_pack to save the string in a variable. My code
#include <string>
void tprintf(std::string& str, const std::string& format)
{
str += format;
}
template <typename T, typename... Targs>
void tprintf(std::string& str, const std::string& format, T arg, Targs ...Fargs)
{
for ( int i = 0; i < format.size(); i++ )
{
if ( format.at(i) == '%' )
{
if (format.at(i + 1) == 'd')
{
std::cout << "== 'd' -variable = " << arg << std::endl;
str += std::to_string(arg);
}
else if (format.at(i + 1) == 's')
{
std::cout << "== 's'" << std::endl;
str += arg;
}
tprintf(str, (i + 2 < format.size() ? format.substr(i + 2) : ""), Fargs...);
break;
}
str += format.at(i);
}
}
int main()
{
std::string str;
int age = 24;
std::string name("Hugo");
tprintf(str, "Name: %s, age: %d years\n", name, age);
std::cout << "result = " << str << std::endl;
}
When compile the code I get the following error:
error: no matching function for call to ‘to_string(std::__cxx11::basic_string<char>&)’
str += std::to_string(arg);
I thought the parameter pack expands to
tprintf(std::string&, const std::string&, std::string, int)
<- arg
is std::string
tprintf(std::string&, const std::string&, int)
<- arg
is int
and I can call std::to_string(arg)
But it seems that arg
has another type than int
(std::string
or something else)?
When I remove the std::to_string
call I get some cryptic characters.How can I convert the value for the age to a string
and append it to str
?
Upvotes: 1
Views: 107
Reputation: 275230
All branches of an if statement are compiled. The compiler is attempting to pass the string argument to to string, and no overload exists.
The fact that it doesn't happen at runtime is irrelevant. At compile time, the compiler needs to know what
tprintf(str, "Name: %s, age: %d years\n", name, age);
does and what
tprintf(str, "Name: %d, age: %d years\n", name, age);
would do.
Consider not including type information twice. You already know that name is a string and age is an integer, repeating it in the format string is pointless.
I'd advise positional commands in a format string, as that makes localization far more practical as well.
Upvotes: 1
Reputation: 118292
tprintf(std::string&, const std::string&, std::string, int) <- arg is std::string
Correct, therefore, here:
str += std::to_string(arg);
arg
is a std::string
, and there is no such std::to_string
overload.
No matter what type T
is, the resulting template must be valid C++ code. Even if the corresponding formatting character is d
, everything in the function must still be valid C++ code.
In general, attempting to implement C-style printf
formatting, in type-safe C++ doesn't make much sense. You already know what needs to be formatted simply by the virtue of the fact that you know the type of the corresponding parameter.
The only reason you have different formatting specifiers in C-style printf
strings, such as %s
or %d
, is because this C function has no clue, whatsoever, what is getting to passed to it, as a parameter, so it relies on the actual formatting specifier to know what it is.
This obviously isn't the case with C++. Your formatting specifier can be just a %
, by itself, with nothing else. You know exactly what parameter gets passed in. Therefore, it's much simpler to merely define three overloaded functions:
template <typename... Targs>
void tprintf(std::string& str, const std::string& format, int arg, Targs ...args)
{
// Just the code that formats an int
}
template <typename... Targs>
void tprintf(std::string& str, const std::string& format, const std::string &arg, Targs ...args)
{
// Just the code that formats a std::string
}
void tprintf(std::string& str, const std::string& format)
{
// Nothing more to format, just adds everything else in "format" to "str".
}
In the first two cases, all that needs to happen is to search format
for the first %
place-holder by itself, copy everything before prior to it into str
, followed by the formatted parameter, and then recursively re-invoke tprintf
with the remaining args
, and what's left in the format
.
P.S. Using forwarding references, "Targs && ...args", together with std::forward
, will be a follow-up tweak.
Upvotes: 2