Skip to content

Instantly share code, notes, and snippets.

@dwcullop
Created August 2, 2021 23:00
Show Gist options
  • Save dwcullop/aabb2007ba0dcd8c7e80a761545e6ca3 to your computer and use it in GitHub Desktop.
Save dwcullop/aabb2007ba0dcd8c7e80a761545e6ca3 to your computer and use it in GitHub Desktop.
Easy, Old School Printf-Style Formatting for std::string and std::wstring
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FormatString.h : Standard String Formatting Support
//
// Author Name :
// Darrin W. Cullop ([email protected])
//
// License :
// CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
//
// Abstract :
// Create or append to a std::string/std::wstring with simple "PrintF" functionality
// (Simliar to C++20's std::format but using the old-school printf style formatting)
//
// Example Usages:
// std::string str = dwc::FormatString( "Hello, World! The number is %d!", 37 );
// std::wstring strw = dwc::FormatString( L"Hello, World! The number is %d!", 37 );
//
// auto str1 = std::string{"Hello"};
// dwc::AppendFormatString( str1, " %s!", "World" );
// std::cout << str1 << std::endl; // Prints "Hello World!"
//
// auto wstr1 = std::wstring{L"Hello"};
// dwc::AppendFormatString( wstr1, L" %s!", "Nurse" );
// std::wcout << dwc::AppendFormatString(wstr1, L"\r\n").c_str(); // wstr1 becomes L"Hello Nurse!\r\n"
//
// auto mixWidths = std::string{"Q: Can we mix in a Wide String?"};
// dwc::AppendFormatString(mixWidths, "\r\nA: ");
// dwc::AppendFormatString(mixWidths, "[%S]", L"Yup but why would we?");
// dwc::AppendFormatString(mixWidths, "\r\n");
// dwc::AppendFormatString(mixWidths, "(Ya never know what you're gonna need)");
//
// std::string GetDictionaryDisplayString( const std::map<std::string, std::string>& dictionary )
// {
// auto display = dwc::FormatString( "Dictionary contains %u Entries\r\n", dictionary.size() );
// for ( const auto& entry : dictionary )
// {
// const auto& [key, value] = *entry;
// dwc::AppendFormatString( display, "\t[%s] = %s\r\n", key.c_str(), value.c_str() );
// }
// return display;
// }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <string>
#include <cstdio>
#include <cwchar>
#include <cassert>
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// dwc Namespace
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace dwc
{
namespace Details
{
// Formatting Overloads for both types of strings
template<typename... Args>
inline auto Format( char* const buffer, std::size_t size, const char* const format, Args&&... args )
{
return std::snprintf( buffer, size, format, std::forward<Args>( args )... );
}
template<typename... Args>
inline auto Format( wchar_t* const buffer, std::size_t size, const wchar_t* const format, Args&&... args )
{
return std::swprintf( buffer, size, format, std::forward<Args>( args )... );
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename CharType, typename... Args>
[[nodiscard]] auto FormatString( const CharType* const format, Args&&... args ) -> std::basic_string<CharType>
{
using String = std::basic_string<CharType>;
// Get the number of characters needed (without the null)
const auto need = Details::Format( nullptr, 0, format, std::forward<Args>( args )... );
assert( ( need > 0 ) || ( format[0] == CharType{ '\0' } ) );
// Allocate the right amount (but include the null)
auto result = String( need + std::size_t{ 1 }, CharType{ '\0' } );
// Write the characters into the string data
const auto used = Details::Format( result.data(), result.capacity(), format, std::forward<Args>( args )... );
assert( ( used > 0 ) || ( format[0] == CharType{ '\0' } ) );
// Shrink the string down to the perfect size (leaving the null at the end for safety)
result.resize( used );
// Return the result
return result;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename StrType, typename... Args>
auto AppendFormatString( StrType& target,
const typename StrType::value_type* const format,
Args&&... args ) -> StrType&
{
using CharType = typename StrType::value_type;
// Find the spot to insert, either at the null, or at the end
auto offset = target.find_last_of( CharType{ '\0' } );
if ( offset == StrType::npos )
{
offset = target.size();
}
// Determine the length of the string to be appended
const auto need = Details::Format( nullptr, 0, format, std::forward<Args>( args )... );
assert( ( need > 0 ) || ( format[0] == CharType{ '\0' } ) );
// Resize the string to fit the old, the new, and the null
target.resize( offset + need + std::size_t{ 1 }, CharType{ '\0' } );
// Write the string to be appended into the string's buffer
const auto used = Details::Format( target.data() + offset,
target.capacity() - offset,
format,
std::forward<Args>( args )... );
assert( ( used > 0 ) || ( format[0] == CharType{ '\0' } ) );
// Shrink the string down to the perfect size (leaving the null at the end for safety)
target.resize( used + offset );
// Return the string object
return target;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace dwc
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// End of FormatString.h
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment