More C++ Idioms/Tag Dispatching
Intent Edit
Simplify writing multiple SFINAE-constrained overloads
Motivation Edit
Tag dispatching is a useful complement to enable_if
.
It can also be used in conjunction with trailing return type and decltype
. (expression SFINAE)
It is most useful when you have multiple overloads for the same function, and they all have conditions for when they can be called. With just enable_if, you have to test for not just the overloads condition, but also the negation of all the other overloads conditions, lest you get overload ambiguity. Tag dispatch will help reduce the mess:
namespace detail { // tags for dispatching
struct pick_3 {}
struct pick_2 : pick_3 {}
struct pick_1 : pick_2 {}
static pick_1 selector;
// first choice - member preferred if exists
template<typename Cont, typename Op>
auto remove_if(pick_1, Cont& cont, Op&& op)
-> decltype(cont.remove_if(op), void())
{
cont.remove_if(std::forward<Op>(op));
}
// second choice - erase remove idiom
template<typename Cont, typename Op>
auto remove_if(pick_2, Cont& cont, Op&& op)
-> decltype(cont.erase(std::remove_if(std::begin(cont), std::end(cont), std::forward<Op>(op)), std::end(cont)), void())
{
cont.erase(std::remove_if(std::begin(cont), std::end(cont), std::forward<Op>(op)), std::end(cont));
}
// last choice - manual looping
template<typename Cont, typename Op>
auto remove_if(pick_3, Cont& cont, Op&& op)
-> void
{
auto it = std::begin(cont);
while (it != std::end(cont))
{
if (op(*it))
it = cont.erase(it);
else
++it;
}
}
}
template<typename Cont, typename Op>
auto remove_if(Cont& cont, Op&& op)
{
detail::remove_if(detail::selector, cont, std::forward<Op>(op));
}
This works because exact match is a better match than a base-class, which in turn is a better match than base of base, etc.
Tag dispatch can also be used when the tag carries useful information, not just a preference ordering.
For example 'dispatching' on std::iterator_traits<It>::iterator_category{}
and have different algorithms for std::random_access_iterator_tag
and std::forward_iterator_tag