A sample Money class

The very reason that we can design a Money class before we know the details of moneypunct customization is because the Money class can remain completely ignorant of this customization. This Money class is meant only to demonstrate I/O. Therefore it is as simple as possible. We begin with a simple struct:

Listing: A example demonstration of input and output
struct Money
{

   long double amount_;

};

// The I/O methods for this class follow a fairly standard formula, 

// but reference the money facets to do the real work:

template<class charT, class traits>

std::basic_istream<charT,traits>&

operator >>(std::basic_istream<charT,traits>& is, Money& item)

{

   typename std::basic_istream<charT,traits>::sentry ok(is);

   if (ok)

   {

   std::ios_base::iostate err = std::ios_base::goodbit;

      try

      {

         const std::money_get<charT>& mg =

            std::use_facet<std::money_get<charT> > (is.getloc());

         mg.get(is, 0, false, is, err, item.amount_);

      }

      catch (...)

      {

         err |= std::ios_base::badbit | std::ios_base::failbit;

      }

      is.setstate(err);

   }

   return is;

}

template<class charT, class traits>

std::basic_ostream<charT, traits>&

operator <<(std::basic_ostream<charT, traits>& os, 

         const Money& item)

{

   std::basic_ostream<charT, traits>::sentry ok(os);

   if (ok)

   {

      bool failed;

      try

      {

         const std::money_put<charT>& mp =

            std::use_facet<std::money_put<charT> >(os.getloc());

         failed = mp.put(os, false, os, os.fill(),

            item.amount_).failed();

   }

      catch (...)

      {

         failed = true;

      }

      if (failed)

         os.setstate(std::ios_base::failbit |

               std::ios_base::badbit);

   }

   return os;

}

The extraction operator ( >>) obtains a reference to money_get from the stream's locale, and then simply uses its get method to parse directly into Money's amount_. The insertion operator ( <<) does the same thing with money_put and its put method. These methods are extremely flexible, as all of the formatting details (save one) are saved in the stream's locale. That one detail is whether we are dealing a local currency format, or an international currency format. The above methods hard wire this decision to "local" by specifying false in the get and put calls. The moneypunct facet can store data for both of these formats. An example difference between an international format and a local format is the currency symbol. The US local currency symbol is "$", but the international US currency symbol is "USD ".

For completeness, we extend this example to allow client code to choose between local and international formats via a stream manipulator. See Matt Austern's excellent C/C++ Users Journal article: The Standard Librarian: User-Defined Format Flags for a complete discussion of the technique used here.

To support the manipulators, our simplistic Money struct is expanded in the following code example.

Listing: Example of manipulator support.

struct Money
{

   enum format {local, international};

   static void set_format(std::ios_base& s, format f)

      {flag(s) = f;}

   static format get_format(std::ios_base& s)

      {return static_cast<format>(flag(s));}

   static long& flag(std::ios_base& s);

   long double amount_;

};

An enum has been added to specify local or international format. But this enum is only defined within the Money class. There is no format data member within Money. That information will be stored in a stream by clients of Money. To aid in this effort, three static methods have been added: set_format, get_format and flag. The first two methods simply call flag which has the job of reading and writing the format information to the stream. Although flag is where the real work is going on, its definition is surprisingly simple.

Listing: Money class flag

long&
Money::flag(std::ios_base& s)

{

   static int n = std::ios_base::xalloc();

   return s.iword(n);

}

As described in Austern's C/C++ User Journal article, flag uses the stream's xalloc facility to reserve an area of storage which will be the same location in all streams. And then it uses iword to obtain a reference to that storage for a particular stream. Now it is easier to see how set_format and get_format are simply writing and reading a long associated with the stream s.

To round out this manipulator facility we need the manipulators themselves to allow client code to write statements like:

  in >> international >> money;
  out << local << money << '\n';
  

These are easily accomplished with a pair of namespace scope methods:

Listing: Money class manipulators

template<class charT, class traits>
std::basic_ios<charT, traits>&
local(std::basic_ios<charT, traits>& s)

{
   Money::set_format(s, Money::local);
   return s;
}

template<class charT, class traits>
std::basic_ios<charT, traits>&
international(std::basic_ios<charT, traits>& s)

{
   Money::set_format(s, Money::international);
   return s;
}

And finally, we need to modify the Money inserter and extractor methods to read this information out of the stream, instead of just blindly specifying false (local) in the get and put methods.

Listing: Money class inserters and extractors

template<class charT, class traits>
std::basic_istream<charT,traits>&
operator >>(std::basic_istream<charT,traits>& is, Money& item)

{
   typename std::basic_istream<charT,traits>::sentry ok(is);
   if (ok)
   {
      std::ios_base::iostate err = std::ios_base::goodbit;
      try
      {
         const std::money_get<charT>& mg = 

         std::use_facet<std::money_get<charT> > (is.getloc());

         mg.get(is, 0, Money::get_format(is) ==

            Money::international, is, err, item.amount_);
      } catch (...) 

         {
            err |= std::ios_base::badbit |
            std::ios_base::failbit;
         }

      is.setstate(err);

   }
   return is;
}
template<class charT, class traits>

std::basic_ostream<charT, traits>&

operator <<(std::basic_ostream<charT, traits>& os, 

   const Money& item)

{
   std::basic_ostream<charT, traits>::sentry ok(os);

   if (ok)

   {
      bool failed;
      try
      {
         const std::money_put<charT>& mp =

            std::use_facet<std::money_put<charT> >(os.getloc());

         failed = mp.put(os, Money::get_format(os) ==

            Money::international, os, os.fill(),

            item.amount_).failed();
      }

      catch (...)

      {
         failed = true;
      }

      if (failed)
         os.setstate(std::ios_base::failbit |
               std::ios_base::badbit);
   }
   return os;
}

Because we gave the enum Money::local the value 0, this has the effect of making local the default format for a stream.

We now have a simple Money class that is capable of culturally sensitive input and output, complete with local and international manipulators! To motivate the following sections on how to customize moneypunct data. Below is sample code that uses our Money class, along with the named locale facility:

Listing: Example of using a money class

int main()
{
   std::istringstream in("USD (1,234,567.89)");

   Money money;

   in >> international >> money;

   std::cout << std::showbase << local << money << '\n';

   std::cout << international << money << '\n';

   std::cout.imbue(std::locale("Norwegian"));

   std::cout << local << money << '\n';

   std::cout << international << money << '\n';

}

And the output is:

  $-1,234,567.89
  USD (1,234,567.89)
   -1 234 567,89 kr
  NOK (1 234 567,89)