-
-
Save Zitrax/a2e0040d301bf4b8ef8101c0b1e3f1d5 to your computer and use it in GitHub Desktop.
#include <string> | |
#include <iostream> | |
#include <memory> | |
/** | |
* Convert all std::strings to const char* using constexpr if (C++17) | |
*/ | |
template<typename T> | |
auto convert(T&& t) { | |
if constexpr (std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, std::string>::value) { | |
return std::forward<T>(t).c_str(); | |
} | |
else { | |
return std::forward<T>(t); | |
} | |
} | |
/** | |
* printf like formatting for C++ with std::string | |
* Original source: https://stackoverflow.com/a/26221725/11722 | |
*/ | |
template<typename ... Args> | |
std::string stringFormatInternal(const std::string& format, Args&& ... args) | |
{ | |
const auto size = snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args) ...) + 1; | |
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); } | |
std::unique_ptr<char[]> buf(new char[size]); | |
snprintf(buf.get(), size, format.c_str(), args ...); | |
return std::string(buf.get(), buf.get() + size - 1); | |
} | |
template<typename ... Args> | |
std::string stringFormat(std::string fmt, Args&& ... args) { | |
return stringFormatInternal(fmt, convert(std::forward<Args>(args))...); | |
} | |
int main() { | |
std::string s = "a"; | |
const std::string cs = "b"; | |
const char* cc = "c"; | |
int i = 1; | |
const int i2 = 2; | |
using namespace std::literals; | |
std::cout << stringFormat("%s %s %s %s %s %d %d %d \n", s, cs, cc, "d", "e"s, i, i2, 3); | |
return 0; | |
} |
Thanks @iFreilicht, I updated it.
I think there's a bug in the use of size_t. It's unsigned, so if snprintf returns an error, you won't see it. You should probably use ssize_t instead. Here's a small program to illustrate the difference:
`
#include
int main () {
size_t length = -1;
std::cout << "size_t: " << length << "\n";
std::cout << "size_t: " << std::boolalpha << (length < 0) << "\n";
ssize_t slength = -1;
std::cout << "ssize_t: " << slength << "\n";
std::cout << "ssize_t: " << std::boolalpha << (slength < 0) << "\n";
}
`
I think there's a bug in the use of size_t. It's unsigned, so if snprintf returns an error, you won't see it.
Thanks, that makes sense. snprintf
actually returns an int
, but I updated the gist to use auto
such that it will be in sync with whatever type snprintf uses.
I want to let you know that I incorporated a slightly robustified version of this code in a library I'm working on here: https://github.com/Dyrcona/libunistdcpp/blob/devel/include/unistd/asprintf.h
If you want me to remove it, I will.
Do you want to be credited in the README, or have a particular copyright statement you want added?
Thats fine 👍 I have no license on this gist - but nice if you link back to it.
This crashes with std::string_view in C++ 20
This crashes with std::string_view in C++ 20
Yeah, and it is probably not possible to fix it safely in the convert function without introducing a memory leak.
I suggest you static_cast your string_view to string when passing them into stringFormat.
Edit: I tried for a couple of hours to make it work with my alternate implementation over here: https://github.com/Dyrcona/libunistdcpp/blob/devel/include/unistd/asprintf.h. The only thing that worked was to use the unsafe (in this context) string_view::data() function.
Maybe someone better at C++ than I can get it to work without a static variable or a memory leak, but most of the things I tried produced gibberish.
I was about to say that the solution is not simple, but maybe it is so simple that it evades me. I did try a few "obvious" things and they didn't work.
The static_cast that I suggested is recommended for other cases where you don't control the API. It's simple, and it works.
After not thinking about this for the rest of the day, I came up with this:
It seems to work for me. The only question I have is does it introduce a memory leak with the std::string s? I'm thinking not since it is a local variable.
Also, if I'm bugging you with these comments, let me know, and I'll go away. :)
Hi! I just updated my answer on SO to throw an exception in case the formatting failed. Depending on the error-code, this could lead to a huge buffer being allocated.
The only things added are
between lines 26 and 27, and an include for the exception:
#include <stdexcept>
This gist is still linked from my answer, so it would be cool if you could update it to keep it in sync :)