1. Syntax
The dest-expr is an expression that defines where the
content goes to as well as the return type of the whole expression above.
The library provides many options to be used as the dest-expr,
and you can even define your own.
However, for convenience, most code samples in this tutorial use to_string
:
#include <strf/to_string.hpp>
void sample() {
int x = 200;
std::string str = strf::to_string(x, " in hexadecimal is ", strf::hex(x));
assert(str == "200 in hexadecimal is c8");
}
You can see that there is no format string here, as there is in printf
.
Instead, format functions ( like hex
above ) specify formatting.
The expression strf::hex(x)
is equivalent to strf::fmt(x).hex()
.
The return of strf::fmt(x)
is an object containing the value of x
in addition to
format information which can be edited with member ( format ) functions
following the
named parameter idiom
, like this: strf::fmt(255).hex().p(4).fill(U'.') > 10
To use a translation tools like
gettext,
you need to use the tr
function,
which employs what is called here as the tr-string:
auto s = strf::to_string.tr("{} in hexadecimal is {}", x, strf::hex(x));
The reserve
, no_reserve
and reserve_calc
functions are only available for some
dest-exprs, to_string
being one of them.
Using reserve(size)
causes the destination to reserve enough space
to store size
characters. reserve_calc()
has the same effect,
except that it calculates the number of characters for you.
1.1. Facets
The with
function receives facet objects,
which somehow complement format functions. They also influence
how the data is printed. A facet example is the lettercase
enumeration:
namespace strf {
enum class lettercase { /* ... */ };
constexpr lettercase lowercase = /* ... */;
constexpr lettercase mixedcase = /* ... */;
constexpr lettercase uppercase = /* ... */;
}
It affects numeric and boolean values:
auto str_uppercase = strf::to_string.with(strf::uppercase)
( true, ' ', *strf::hex(0xab), ' ', 1.0e+50 );
auto str_mixedcase = strf::to_string.with(strf::mixedcase)
( true, ' ', *strf::hex(0xab), ' ', 1.0e+50 );
assert(str_uppercase == "TRUE 0XAB 1E+50");
assert(str_mixedcase == "True 0xAB 1e+50");
1.2. Constrained facets
You can constrain facets to a set of input types:
auto str = strf::to_string
.with(std::constrain<std::is_floating_point>(strf::uppercase))
( true, ' '*strf::hex(0xab), ' ', 1.0e+50 );
assert(str == "true 0xab 1E+50");
, or to a set of arguments:
auto str = strf::to_string
( true, ' ', 1.0e+50, " / "
, strf::with(strf::uppercase) (true, ' ', 1.0e+50, " / ")
, true, ' ', 1.0e+50 );
assert(str == "true 1e+50 / TRUE 1E+50 / true 1e+50 );
When there are multiple facets objects of the same category, the order matters. The later one wins:
auto fa = strf::mixedcase;
auto fb = std::constrain<std::is_floating_point>(strf::uppercase);
using namespace strf;
auto str_ab = to_string .with(fa, fb) (true, ' ', *hex(0xab), ' ', 1e+9);
auto str_ba = to_string .with(fb, fa) (true, ' ', *hex(0xab), ' ', 1e+9);
// In str_ab, fb overrides fa, but only for floating points
// In str_ba, ba overrides fb for all types, so fb has no effect.
assert(str_ab == "True 0xAB 1E+9");
assert(str_ba == "True 0xAB 1e+9");
1.3. Facets categories
But what does it mean for two facet objects to belong to same facet category?
In this library, the term facet always refers to types. So the type
strf::lettercase
is a facet, while strf::uppercase
is a facet value.
In addition, a facet is always associated to one, and only one, facet category.
However, several facets can "belong" to the same category.
For each facet category there is class or struct
with a public static member function get_default()
which
returns the default facet value of such facet category.
By convention, the name of such class or struct is the name of the
category, and it has the “_c” suffix.
For example, the category of strf::lettercase
is strf::lettercase_c
,
and strf::lettercase_c::get_default()
returns strf::lowercase
.
Informaly ( perhaps in future it will be formal thanks to C++20 Concepts )
for each facet category there is a list of requirements a type
must satisfy to be a facet of the category. In the case of
strf::lettercase_c
, the requirement is, well, to be the
strf::lettercase
type, since this is only facet of this category
by design. However other categories require the facet to
contain member functions with specified signatures, effects,
preconditions, posconditions and so on.
The design of the facets varies a lot according to their categories.
But all facets currently available in the library have something in common:
they all are small types ( in terms of sizeof()
) and provide a fast
copy constructor.
In addition, most of them can be instantiated as constexpr
values.
The facet_traits
struct template provides the category a given facet.
1.4. Facets packs
To avoid retyping all the facets object that you commonly use,
you can store them into a facets_pack
,
which you create with the pack
function template:
constexpr auto my_facets = strf::pack
( strf::mixedcase
, std::constrain<strf::is_bool>(strf::uppercase)
, strf::numpunct<10>{3}.thousands_sep(U'.').decimal_point(U',')
, strf::numpunct<16>{4}.thousands_sep(U'\'')
, strf::windows_1252<char> );
auto str1 = strf::to_string.with(my_facets) (/* ... */);
// ...
auto str2 = strf::to_string.with(my_facets) (/* ... */);
// ...
Any value that can be passed to the with
function, can also be passed to pack
,
and vice-versa. This means a facets_pack
can contain another facets_pack
.
So the expression:
dest-expr.with(f1, f2, f3, f4, f5) (/* args... */);
is equivalent to
dest-expr.with(strf::pack(f1, strf::pack(f2, f3), f4), f5) (/* args... */);
, which is also equivalent to:
dest-expr.with(f1).with(f2).with(f3).with(f4).with(f5) (/* args... */);
1.5. Locales
Strf is a locale-independent library. When you don’t specify any facet
object, everything is printed as in the "C" locale.
However, the header <strf/locale.hpp>
provides the function locale_numpunct
that returns a numpunct<10>
object that reflects the numeric punctuation of
the current locale ( decimal point, thousands separator and digits grouping ).
locale_numpunct()
is not thread safe. Actually using locales
in general is not thread safe. However, once you store its returned
value into a numpunct<10>
object, that object is not affected anymore when
the locale changes. Also, numpunct<10>
is a facet.
#include <strf/locale.hpp>
#include <strf/to_string.hpp>
void sample() {
if (setlocale(LC_NUMERIC, "de_DE")) {
const auto punct_de = strf::locale_numpunct();
auto str = strf::to_string.with(punct_de) (*strf::fixed(10000.5))
assert(str == "10.000,5");
// Changing locale does not affect punct_de
// So using it is thread safe
setlocale(LC_NUMERIC, "C");
auto str2 = strf::to_string.with(punct_de) (*strf::fixed(20000.5));
assert(str2 == "20.000,5");
}
}
2. Other destinations
Up to here, we only covered things that define the
content to be printed, not where it is printed.
The quick_reference provides a
list of expressions
that can be used instead of to_string
, so that you can
select alternative destinations.
Now, every one of these expressions internally relies on a
concrete class that derives from the destination
abstract
class template. These classes are also
documented.
This means that you can write a function or code
that writes to a reference of destination
, so that
it works with all kind of destinations:
void get_message(strf::destination<char>& dest)
{
strf::to(dest) ("Hello World!");
}
Hence, in a sense, strf::destination
is equivalent
to std::basic_ostream
or std::basic_streambuf
,
but with better performance, and it’s easier to implement
a class that derives from strf::destination
than from std::basic_ostream
or std::basic_streambuf
.