In the last post we looked at different ways of overloading implementing the advance function. This time we will look at a more complicated example. We are going to implement a print function that prints recursively over ranges, fusion sequences, and variants. Let’s take a look how we print each of these on their own, first.

Ranges

First, let’s define a trait to check for a range. A range is a pair of iterators, so first lets define a trait to check for an iterator:

TICK_TRAIT(is_iterator, std::is_copy_constructible<_>, std::is_copy_assignable<_>, std::is_destructible<_>) { template<class T> auto require(T x) -> valid< decltype(*x), decltype(returns<T&>(++x)) >; };

Next, we will check that the begin and end functions return back iterators. We have to do some dancing around so that the begin and end functions are found by ADL lookup:

namespace adl { using std::begin; using std::end; template<class R> auto adl_begin(R&& r) -> decltype(begin(r)); template<class R> auto adl_end(R&& r) -> decltype(end(r)); } TICK_TRAIT(is_range) { template<class T> auto require(T&& x) -> valid< decltype(returns<is_iterator<_>>(adl::adl_begin(x))), decltype(returns<is_iterator<_>>(adl::adl_end(x))) >; };

Next, we print it like this:

template<class Range, TICK_REQUIRES(is_range<Range>())> void print(const Range& r) { for(const auto& x:r) std::cout << x << std::endl; } std::vector<int> r = { 3, 3, 3 }; print(r);

Fusion sequences

Well boost provides a trait to detect that it is a fusion sequence, and we use for_each to iterate over the sequence:

template<class Sequence, TICK_REQUIRES(boost::fusion::traits::is_sequence<Sequence>())> void print(const Sequence& s) { boost::fusion::for_each(s, [](const auto& x) { std::cout << x << std::endl; }); }

Variant

To print a variant we use the visitor pattern:

template<class... Ts> void print(const boost::variant<Ts...>& v) { boost::apply_visitor(fit::result<void>([](const auto& x) { std::cout << x << std::endl; }), v); }

Streamable

For everything else, we check that it is streamable:

TICK_TRAIT(is_streamable) { template<class Stream, class T> auto require(Stream&& s, T&& x) -> valid< decltype(s << x) >; }; template<class T, TICK_REQUIRES(is_streamable<std::ostream, T>())> void print(const T& x) { std::cout << x << std::endl; }

Overlap

Although, each one is unrelated there is some overlap between fusion sequences, ranges, and strings. So we need to compensate for this as well.

Function overloading

For function overloading we will need to negate the other overloads that might overlap. Since we are calling the functions recursively, we need to forward declare each overload as well. Unfortunately, we can’t forward declare using TICK_REQUIRES in the template parameters. We need to use TICK_FUNCTION_REQUIRES instead. Also, we have to declare an extra overload for std::string so it doesn’t get printed as a range:

void print(const std::string& x) { std::cout << x << std::endl; } template<class Range> TICK_FUNCTION_REQUIRES(is_range<Range>() and not std::is_convertible<Range, std::string>()) (void) print(const Range& r); template<class Sequence> TICK_FUNCTION_REQUIRES(boost::fusion::traits::is_sequence<Sequence>() and not is_range<Sequence>()) (void) print(const Sequence& s); template<class... Ts> void print(const boost::variant<Ts...>& v); template<class T> TICK_FUNCTION_REQUIRES(is_streamable<std::ostream, T>() and not boost::fusion::traits::is_sequence<T>() and not is_range<T>()) (void) print(const T& x); template<class Range> TICK_FUNCTION_REQUIRES(is_range<Range>() and not std::is_convertible<Range, std::string>()) (void) print(const Range& r) { for(const auto& x:r) print(x); } template<class Sequence> TICK_FUNCTION_REQUIRES(boost::fusion::traits::is_sequence<Sequence>() and not is_range<Sequence>()) (void) print(const Sequence& s) { boost::fusion::for_each(s, [](const auto& x) { print(x); }); } template<class... Ts> void print(const boost::variant<Ts...>& v) { boost::apply_visitor(fit::result<void>([](const auto& x) { print(x); }), v); } template<class T> TICK_FUNCTION_REQUIRES(is_streamable<std::ostream, T>() and not boost::fusion::traits::is_sequence<T>() and not is_range<T>()) (void) print(const T& x) { std::cout << x << std::endl; }

Of course, this is a lot of boilerplate, and its difficult to maintain.

Tag-dispatching

Using tag dispatching we define tags for the overlap of ranges and fusion sequences:

struct base_tag {}; struct sequence_tag : base_tag {}; struct range_tag : sequence_tag {};

Then we can write a metafunction to retrieve the tag:

template<class T, class=void> struct get_print_tag { using type = base_tag; }; template<class Range> struct get_print_tag<Range, TICK_CLASS_REQUIRES(is_range<Range>() and not std::is_convertible<Range, std::string>())> { using type = range_tag; }; template<class Sequence> struct get_print_tag<Sequence, TICK_CLASS_REQUIRES(boost::fusion::traits::is_sequence<Sequence>() and not is_range<Sequence>())> { using type = sequence_tag; };

Now even though this logic is little complicated it only needs to be written once. Now we can write the print function:

template<class T> void print(const T& x); void print_impl(const std::string& x, base_tag) { std::cout << x << std::endl; } template<class Range> void print_impl(const Range& r, range_tag) { for(const auto& x:r) print(x); } template<class Sequence> void print_impl(const Sequence& s, sequence_tag) { boost::fusion::for_each(s, [](const auto& x) { print(x); }); } template<class... Ts> void print_impl(const boost::variant<Ts...>& v, base_tag) { boost::apply_visitor(fit::result<void>([](const auto& x) { print(x); }), v); } template<class T> void print_impl(const T& x, base_tag) { std::cout << x << std::endl; } template<class T> void print(const T& x) { print_impl(x, typename get_print_tag<T>::type()); }

Now this is a littler nicer, because we don’t have to forward declare each of the overloads, but we still have to write some boilerplate for dispatching. Plus, it doesn’t seem to make much sense to have hierarchal relationships, because a range is not a refinement of a fusion sequence, and if we had another overload, we might have to restructure the entire tag dispatching.

Ranking

We can also use Xeo’s ranking described in his post here:

template<int N> struct rank : rank<N-1> {}; template<> struct rank<0> {}; template<class T> void print(const T& x); template<class T> TICK_FUNCTION_REQUIRES(std::is_convertible<T, std::string>()) (void) print_impl(const T& x, rank<5>) { std::cout << x << std::endl; } template<class Range> TICK_FUNCTION_REQUIRES(is_range<Range>()) (void) print_impl(const Range& r, rank<4>) { for(const auto& x:r) print(x); } template<class Sequence> TICK_FUNCTION_REQUIRES(boost::fusion::traits::is_sequence<Sequence>()) (void) print_impl(const Sequence& s, rank<3>) { boost::fusion::for_each(s, [](const auto& x) { print(x); }); } template<class... Ts> void print_impl(const boost::variant<Ts...>& v, rank<2>) { boost::apply_visitor(fit::result<void>([](const auto& x) { print(x); }), v); } template<class T> TICK_FUNCTION_REQUIRES(is_streamable<std::ostream, T>()) (void) print_impl(const T& x, rank<1>) { std::cout << x << std::endl; } template<class T> void print(const T& x) { print_impl(x, rank<5>()); }

In this we have to make the std::string overload a template instead, otherwise it will be an ambiguous overload. Now this may be a little simpler than previous solutions, but it still requires dispatching boilerplate every time, and adding new overloads requires reordering the ranking.

Conditional Overloading

Conditional overloading from the Fit library can be used. Now, to make it recursive, we can use the fix adaptor which passes in itself as the first parameter. This is especially useful since we define the functions as lambdas. Also, since we use auto for the template parameters, we define a trait to match the boost::variant type as well:

template<class T> struct is_variant : std::false_type {}; template<class T> struct is_variant<const T> : is_variant<T> {}; template<class... Ts> struct is_variant<boost::variant<Ts...>> : std::true_type {}; const constexpr auto print = fit::fix(fit::conditional( FIT_STATIC_LAMBDA(auto, const std::string& x) { std::cout << x << std::endl; }, FIT_STATIC_LAMBDA(auto self, const auto& range, TICK_PARAM_REQUIRES(tick::trait<is_range>(range))) { for(const auto& x:range) self(x); }, FIT_STATIC_LAMBDA(auto self, const auto& sequence, TICK_PARAM_REQUIRES(tick::trait<boost::fusion::traits::is_sequence>(sequence))) { boost::fusion::for_each(sequence, self); }, FIT_STATIC_LAMBDA(auto self, const auto& variant, TICK_PARAM_REQUIRES(tick::trait<is_variant>(variant))) { boost::apply_visitor(fit::result<void>(self), variant); }, FIT_STATIC_LAMBDA(auto, const auto& x, TICK_PARAM_REQUIRES(tick::trait<is_streamable>(std::cout, x))) { std::cout << x << std::endl; } ));

This has several advantages over the other solutions. First, additional overloads can be added easily. Secondly, it doesn’t require a lot of boilerplate every time. It is very nice and compact.