You can add the .c_str()
call to the parameter pack expansion inside the template function.
template<typename ... StringArgs>
std::string
string_format(const std::string& format, StringArgs ... args)
{
std::size_t size = std::snprintf(nullptr, 0, format.c_str(), args.c_str() ...) + 1;
//^^^^^^^^^^^^
std::unique_ptr<char[]> buf(new char[size]);
std::snprintf(buf.get(), size, format.c_str(), args.c_str() ...);
//^^^^^^^^^^^^
return std::string(buf.get(), buf.get() + size - 1);
}
int main(void)
{
std::string h{"hello"};
std::string w{"world"};
std::cout << string_format("%s %s\n", h, w) << std::endl; // This works
// This won't compile
// std::cout << string_format("%d\n", 0) << std::endl;
}
This works because of how packs are expanded. From the docs:
A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order.
There are several examples on that page of applying various transformations to each element in the pack, by doing things like func(pack)...
, which is equivalent to func(item0), func(item1), ...
.
An obvious downside of this modified function is that it will not compile if the parameter pack is not all std::strings
, because the .c_str()
method is applied to every element of the parameter pack on expansion. You could probably figure out some trickery to keep both versions around, though.