C++ facets

Facets are one of the least understood and most esoteric parts of the C++ standard library. Because they are rarely used, there are few code examples, which in turn means most people never encounter them.

This is a great shame! Facets are a fascinating and multifa rich source of coding fun!

Besides reading Angelika Langer's Standard C++ IOStreams and Locales book and reading the Apache C++ Standard Library User's Guide, it's useful to have two basic things: a) a concise description of their purpose, and b) an example of how to use facets. A basic reference implementation is often useful as a working example to start from, or to save starting from scratch every time you use facets.

What are facets for?

They provide ways for formatting and parsing special types such as numbers and currencies. Depending on language, culture, and location, the same value can be written in different ways or has different units. For example, a US bank account might have the following balance:

$300,104.56

In Germany, a bank account with the same number of Euros could be printed like this:

300.104,56 €

Handling these differences is important not only for output, but also for reading values from files or other input streams.

Other local differences apply to sorting text. In many Western countries, text is sorted according to an (approximately) 26-letter alphabet. But even in those countries, some languages have extra letters or diacritical marks such as ø, ä, ß, í or ü. These also need to be sorted in the correct place!

In the past, J and I were considered to be the same letter in German and sorted accordingly. A category of facets called 'collate' is provided to handle such differences.

How can you use facets?

Facets are used together with locales. They are grouped into six standard categories, each of which has up to three facets:

  • numeric
  • monetary
  • time
  • collate
  • messages
  • ctype

Some categories, such as ctype and numeric, are used automatically. Using a locale with a custom numeric facet can change input and output of all numbers. Others, such as the monetary category, are available only for use in your own implementation, for instance when writing a class for handling currency.

The following code shows how to use the std::moneypunct facet with just such a currency class.

#include <locale>
#include <iostream>
#include <iterator>
#include <string>

/// A useful class to represent an amount of currency.
//
/// There is no way to change the amount after construction!
template<typename T>
struct Currency
{
    Currency(T value) : _val(value) {}
    T value() const { return _val; }

private:
    T const _val;
};

/// Our custom moneypunct facet to format currency as it's done in Germany
template<typename charT, bool Intl = false>
struct GermanPunct : public std::moneypunct<charT, Intl>
{
    GermanPunct(size_t refs = 0) : std::moneypunct<charT, Intl>(refs) {}
    virtual ~GermanPunct() {}

protected:
    typedef typename std::moneypunct<charT>::string_type string_type;
    typedef typename std::moneypunct<charT>::char_type char_type;
    typedef typename std::moneypunct<charT>::pattern pattern;

    virtual string_type do_curr_symbol() const { return "€"; }
    virtual char_type do_thousands_sep() const { return '.'; }
    virtual std::string do_grouping() const { return "\003"; }
    virtual string_type do_positive_sign() const { return "+"; }
    virtual string_type do_negative_sign() const { return "-"; }
    virtual char_type do_decimal_point() const { return ','; }
    virtual int do_frac_digits() const { return 2; }
    virtual pattern do_pos_format() const {
        pattern const p = {
            { 
                std::moneypunct<charT>::sign,
                std::moneypunct<charT>::value,
                std::moneypunct<charT>::symbol,
                std::moneypunct<charT>::none
            }
        };
        return p;
    }
    virtual pattern do_neg_format() const {
        return do_pos_format();
    }
};

/// The ostream output operator for our Currency class
template<typename charT, typename T>
std::basic_ostream<charT>&
operator<<(std::basic_ostream<charT>& o, Currency<T> const& c)
{
    typedef typename std::basic_ostream<charT>::char_type char_type;
    typedef std::ostreambuf_iterator<char_type, std::char_traits<char_type> >
        iterator;
    typedef std::money_put<char_type, iterator> money_put;
  
    std::ostream::sentry s(o);
    if (!s) return o;
    
    // Locales are reference counted, so that copying them
    // is generally trivial.
    std::locale loc = o.getloc();
    money_put const& mp = std::use_facet<money_put>(loc);

    iterator begin(o);
    mp.put(begin, false, o, ' ', c.value());

    return o;
}

int main()
{
    // Create a locale based on the current output locale, but with
    // the monetary facet replaced by our custom one.
    // Note that facets are reference-counted by the standard library. 
    // By default they will be destroyed when no references remain.
    std::locale loc(std::cout.getloc(), new GermanPunct<char>());
    std::cout.imbue(loc);

    Currency<int> const t(13453334);
    Currency<int> const t2(-13453334);

    // The currency symbol is only displayed when showbase
    // is active.
    std::cout << std::showbase << t << "\n";
    std::cout << std::showbase << t2 << "\n";
}

Output:

+134.533,34€
-134.533,34€

Many thanks

Exaustive and complete explanation and example. Thank you.
I used it in a function to return the formatted string: (thanks also to www.cplusplus.com)


std::string FormatNumber(long Number){
Currency const qty(Number);
std::stringstream Convert;
std::locale MyLocale( std::locale(), new GermanPunct() ); // Create customized locale
Convert.imbue(MyLocale); // Imbue the custom locale to the stringstream
Convert << std::showbase << std::fixed << qty; // Use some manipulators
return Convert.str(); // Give the result to the string
}