1. Syntax

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.