More C++ Idioms/Tag Dispatching

From Wikibooks, open books for an open world
Jump to navigation Jump to search
==Tag dispatch==

Intent[edit | edit source]

Simplify writing multiple SFINAE-constrained overloads

Motivation[edit | edit source]

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