Skip to content

Instantly share code, notes, and snippets.

@Zitrax
Last active November 7, 2023 23:06
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Zitrax/a2e0040d301bf4b8ef8101c0b1e3f1d5 to your computer and use it in GitHub Desktop.
Save Zitrax/a2e0040d301bf4b8ef8101c0b1e3f1d5 to your computer and use it in GitHub Desktop.
stringformat with constexpr if
#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;
}
@iFreilicht
Copy link

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

if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

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 :)

@Zitrax
Copy link
Author

Zitrax commented Dec 26, 2019

Thanks @iFreilicht, I updated it.

@Dyrcona
Copy link

Dyrcona commented Jun 20, 2022

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";
}

`

@Zitrax
Copy link
Author

Zitrax commented Jun 21, 2022

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.

@Dyrcona
Copy link

Dyrcona commented Sep 3, 2022

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?

@Zitrax
Copy link
Author

Zitrax commented Sep 5, 2022

Thats fine 👍 I have no license on this gist - but nice if you link back to it.

@duong2179
Copy link

This crashes with std::string_view in C++ 20

@Dyrcona
Copy link

Dyrcona commented Nov 7, 2023

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.

@Dyrcona
Copy link

Dyrcona commented Nov 7, 2023

After not thinking about this for the rest of the day, I came up with this:

Dyrcona/libunistdcpp@02cb784

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. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment