These articles are written by Codalogic empowerees as a way of sharing knowledge with the programming community. They do not necessarily reflect the opinions of Codalogic.
A while back I blogged about using std::string
and std::to_string
as a cheap and cheerful way of doing string formatting as an
alternative to using std::ostringstream
and sprintf
.
Now in C++20 we officially have std::format
added. (Note however that
as of writing it is only implemented in Visual Studio MSVC and
not implemented in GCC or Clang.)
std::format
ends up looking much like sprintf
except that all
the arguments are type-safe and it returns a std::string
(or std::wstring
).
In the original blog post I had code like:
fake_widget( std::string( "Can't open file: " ).append( file_name ) );
fake_widget( std::string( "Volume = " ).append( std::to_string( volume ) ) );
With std::format
we can replace this with:
fake_widget( std::format( "Can't open file: {}", file_name ) );
fake_widget( std::format( "Volume = {}", volume ) );
Here the {}
s represent where the parameter values are to be substituted.
For longer messages where previously I did this:
fake_widget( std::string( "You have " )
.append( std::to_string( n_errors ) )
.append( " errors in " )
.append( std::to_string( n_files ) )
.append( " files." ) );
You can now do:
fake_widget( std::format( "You have {} errors in {} files.", n_errors, n_files ) );
Much nicer.
Within the {}
s you can include lots of formatting, including the index of the
argument you want inserted at that position. For example, you can do:
fake_widget( std::format( "{1}, I said {1}, is bigger that {0}.", 2, 3 ) );
Which outputs:
3, I said 3, is bigger that 2.
This ability to change the parameter output order
makes it appealing to consider using std::format
for i18n internationalisation
translation functionality where the translated text may require the parameters
to be used in a different order. It would be nice to be able to do something like
the following
(where translate_fmt()
returns a string in the nationalised language):
// Doesn't compile :(
fake_widget( std::format( translate_fmt( "You have {} errors in {} files." ), n_errors, n_files ) );
Alas, std::format
requires that the format string be provided in a constexpr
context
so the above won't compile.
A workaround is to use the std::vformat
sister function and std::make_format_args
:
fake_widget( std::vformat( translate_fmt( "You have {} errors in {} files." ),
(n_errors, n_files) ) );
But this is pretty ugly! Fortunately adding a template helper function makes this much better:
template <typename... Args>
std::string translate( const std::string_view fmt, Args&&... args )
{
return std::vformat( translate_fmt( fmt ), std::make_format_args( args... ) );
}
Then you can do:
fake_widget( translate( "You have {} errors in {} files.", 8, 4 ) );
Which in this case, due to my quirky translation logic, outputs:
In 4 files you have made 8 errors.
The whole test program is below and can be played with at: https://godbolt.org/z/n3ofTo8rT
#include <iostream>
#include <string>
#include <string_view>
#include <format>
void fake_widget( const std::string & s )
{
std::cout << s << "\n";
}
// Pretend these are set earlier in the application
std::string file_name{ "notes.txt" };
int volume = 6;
int n_errors = 8;
int n_files = 4;
const char * translate_fmt( const std::string_view text )
{
// Translations would normally be loaded from a translation file,
// so this function can't be constexpr. But for now...
if( text == "You have {} errors in {} files." )
return "In {1} files you have made {0} errors.";
return text.data();
}
template <typename... Args>
std::string translate( const std::string_view fmt, Args&&... args )
{
return std::vformat( translate_fmt( fmt ), std::make_format_args( args... ) );
}
int main()
{
// Examples from the previous blog
fake_widget( std::format( "Can't open file: {}", file_name ) );
fake_widget( std::format( "Volume = {}", volume ) );
// A longer message
fake_widget( std::format( "You have {} errors in {} files.", n_errors, n_files ) );
// The braces in the format can include foratting instructions,
// including positional indicators. e.g. Let's reverse the arguments
fake_widget( std::format( "{1}, I said {1}, is bigger that {0}.", 2, 3 ) );
// Thus potentially std::format could also help with message internationalisation
// but the format supplied to std::format must be const so this doesn't work
//fake_widget( std::format( translate_fmt( "You have {} errors in {} files." ), n_errors, n_files ) );
// Instead we can do this, but it is a bit messy
fake_widget( std::vformat( translate_fmt( "You have {} errors in {} files." ),
std::make_format_args(n_errors, n_files) ) );
// To compensate we can write a helper template function
fake_widget( translate( "You have {} errors in {} files.", 8, 4 ) );
}
This outputs the following:
Can't open file: notes.txt
Volume = 6
You have 8 errors in 4 files.
3, I said 3, is bigger that 2.
In 4 files you have made 8 errors.
In 4 files you have made 8 errors.
As std::format
isn't in GCC and Clang yet, maybe it's a bit too early to
get excited about it, but I think it will be very beneficial in the future.
Before finishing it's perhaps worth confirming that you can obviously use
std::format()
in other places, such as with std::cout
:
std::cout << std::format( "You have {} errors in {} files.", n_errors, n_files );
February 2023
January 2023
December 2022
November 2022
October 2022
September 2022
August 2022
November 2021
June 2021
May 2021
April 2021
March 2021
October 2020
September 2020
September 2019
March 2019
June 2018
June 2017
August 2016