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:
std::copy(
headers.begin(), headers.end()
, fastformat::format_iterator(
stm
, "{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);
std::copy(
headers.begin(), headers.end()
, fastformat::format_iterator(
std::cout
, "{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 www.fastformat.org), 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
{
public:
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;
}
public:
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_())
{}
format_output_iterator(
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)
{
switch(m_n)
{
case 0:
fmt(m_sink, m_format, value);
break;
case 1:
fmt(m_sink, m_format, value
, impl::ref_helper<A1>::get_ref(m_arg1));
break;
case 2:
fmt(m_sink, m_format, value
, impl::ref_helper<A1>::get_ref(m_arg1)
, impl::ref_helper<A2>::get_ref(m_arg2));
break;
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));
break;
. . . 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);
}
. . .
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.)


