Channels ▼


C++ and format_iterator

Version 2 Implementation: Multiple Format Parameters

The version shown in Listing 6 is a fully-fledged output iterator. But we still have the issue of having manually concatenated the prefix and suffix to the format replacement parameter to form the format string in application code. It would be nice to be able instead to write the format statements along the lines of:

  headers.begin(), headers.end()
, fastformat::format_iterator(
  , "{1}{0}{2}"
  , prefix
  , suffix));

The zero'th replacement parameter always represents the current sequence element; the others are 1-based indexes of the additional arguments passed to format_iterator().

Of course, once we can do two additional arguments, why not allow more values from the calling context be included in the logging statement, as in:

std::string const prefix("\t");
char const* const suffix = "\n";

void f(std::string const& dir)
  recls::search_sequence headers(dir, "*.h|*.hpp|*.hxx", recls::RECURSIVE);

    headers.begin(), headers.end()
  , fastformat::format_iterator(
    , "{1}{0} found in {3}{2}"
    , prefix
    , suffix
    , dir));

This can give output such as the following:

H:\freelibs\b64\1.4\include\b64\b64.h found in H:\freelibs\b64\1.4
H:\freelibs\b64\1.4\include\b64\implicit_link.h found in H:\freelibs\b64\1.4
H:\freelibs\b64\1.4\include\b64\b64.hpp found in H:\freelibs\b64\1.4
. . .

To support multiple replacement parameters requires quite a bit more cunning (and a bit of template meta-programming). I'm not going to show the full implementation, as it is available in the FastFormat distribution (via, but I will illustrate the technique with the relevant sections of the class template, creator function template(s), and worker types, shown in Listing 7.

namespace impl
  struct no_arg_t
  template <typename T>
  struct ref_helper
    static T const& get_ref(T const& t)
      return t;
  template <>
  struct ref_helper<no_arg_t>
    static ff_char_t const* get_ref(no_arg_t const&)
      static ff_char_t s_empty[] = { '\0' };
      return s_empty;
} /* namespace impl */

template< typename S
        , typename F
        , typename A1
        , typename A2
        , typename A3
        , typename A4
        , typename A5
        , typename A6
        , typename A7
        , typename A8
class format_output_iterator
  : . . .
  . . .
private: // Construction
  static impl::no_arg_t const& no_arg_ref_()
    // NOTE: this is a race-condition, but it's entirely benign
    static impl::no_arg_t r;
    return r;
  format_output_iterator(sink_type& sink, format_type const& format)
    : m_sink(sink)
    , m_format(::stlsoft::c_str_data(format), ::stlsoft::c_str_len(format))
    , m_n(0)
    , m_arg1(no_arg_ref_())
    , m_arg2(no_arg_ref_())
    , m_arg3(no_arg_ref_())
    , m_arg4(no_arg_ref_())
    , m_arg5(no_arg_ref_())
    , m_arg6(no_arg_ref_())
    , m_arg7(no_arg_ref_())
    , m_arg8(no_arg_ref_())
    sink_type&  sink
  , format_type const&  format
  , unsigned    n
  , A1 const&   arg1
  , A2 const&   arg2 = no_arg_ref_()
  , A3 const&   arg3 = no_arg_ref_()
  , A4 const&   arg4 = no_arg_ref_()
  , A5 const&   arg5 = no_arg_ref_()
  , A6 const&   arg6 = no_arg_ref_()
  , A7 const&   arg7 = no_arg_ref_()
  , A8 const&   arg8 = no_arg_ref_()
    : m_sink(sink)
    , m_format(::stlsoft::c_str_data(format), ::stlsoft::c_str_len(format))
    , m_n(n)
    , m_arg1(arg1)
    , m_arg2(arg2)
    , m_arg3(arg3)
    , m_arg4(arg4)
    , m_arg5(arg5)
    , m_arg6(arg6)
    , m_arg7(arg7)
    , m_arg8(arg8)
  . . .
  template <typename A>
  void invoke_(A const& value)
      case    0:
        fmt(m_sink, m_format, value);
      case    1:
        fmt(m_sink, m_format, value
          , impl::ref_helper<A1>::get_ref(m_arg1));
      case    2:
        fmt(m_sink, m_format, value
          , impl::ref_helper<A1>::get_ref(m_arg1) 
          , impl::ref_helper<A2>::get_ref(m_arg2));
      case    3:
        fmt(m_sink, m_format, value
          , impl::ref_helper<A1>::get_ref(m_arg1) 
          , impl::ref_helper<A2>::get_ref(m_arg2) 
          , impl::ref_helper<A3>::get_ref(m_arg3));
        . . . 4 - 7:
  . . .
private: // Fields
  sink_type&         m_sink;
  string_type_ const m_format;
  unsigned const      m_n;
  A1 const&           m_arg1;
  A2 const&           m_arg2;
  A3 const&           m_arg3;
  A4 const&           m_arg4;
  A5 const&           m_arg5;
  A6 const&           m_arg6;
  A7 const&           m_arg7;
  A8 const&           m_arg8;
. . .
template<   typename S
        ,   typename F
        ,   typename A1
        ,   typename A2
        ,   typename A3
        ,   typename A4
inline format_output_iterator<S, F, A1, A2, A3, A4, impl::no_arg_t, impl::no_arg_t, impl::no_arg_t, impl::no_arg_t> format_iterator(
  S&        sink
, F const&  format
, A1 const& arg1
, A2 const& arg2
, A3 const& arg3
, A4 const& arg4
  return format_output_iterator<S, F, A1, A2, A3, A4, impl::no_arg_t, impl::no_arg_t, impl::no_arg_t, impl::no_arg_t>(sink, format, 4u, arg1, arg2, arg3, arg4);
. . .

Listing 7

The iterator class template provides for up to eight additional arguments. (Need for any more than eight seemed unlikely.) There are eight additional creator function templates, taking 1, 2, 3 … 8 additional arguments. Each creator function tells the iterator instance how many parameters are expected, and this is used to select (at runtime) the requisite fmt() statement.

The type fastformat::iterators::impl::no_arg_t is used as a placeholder where a parameter, and therefore a type, is not required, and together with the traits type fastformat::iterators::impl::ref_helper<> is used to ensure that the iterator's invoke_() method compiles: even where the switch cases don't match the n parameter, and therefore will never be executed, they are still compiled. Note the manner in which the ref_helper specialisation creates a thread-safe and linkage-safe empty string, independent of character types; this technique is described in detail in Imperfect C++.

I could have gone further, and used meta-programming techniques to select the requisite fmt() statement at compile-time; I leave that as an exercise for the reader. (If anyone provides an implementation that works with all supported compilers, I'll be happy to consider it for inclusion in the library.)

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.