跳转到内容

更多 C++ 惯用法/标签分发

来自 Wikibooks,开放世界中的开放书籍

标签分发

[编辑 | 编辑源代码]

简化编写多个 SFINAE 约束的重载。

标签分发是 enable_if 的有用补充。它也可以与尾随返回类型和 decltype 结合使用,decltype 使用表达式 SFINAE。

当您对同一函数有多个重载,并且它们都有可以调用的条件时,它最有用。仅使用 enable_if,您必须不仅测试重载条件,还要测试所有其他重载条件的否定,否则您将遇到重载歧义。标签分发将有助于减少混乱

解决方案

[编辑 | 编辑源代码]
namespace detail
{
    // tags for dispatching
    struct pick_3{};
    struct pick_2 : public pick_3{};
    struct pick_1 : public pick_2{};
    constexpr 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(std::forward<Op>(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>
	void remove_if(pick_3, Cont& cont, Op&& op)
	{
		auto it = std::begin(cont);
        auto end = std::end(cont);
		while (it != end)
		{
			if (std::invoke_r<bool>(std::forward<Op>(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));
}

这是因为精确匹配比基类匹配更好,而基类匹配比基类匹配更好,等等。标签分发也可以在标签携带有用信息而不是仅仅是首选项排序时使用。例如,可以根据 typename std::iterator_traits<It>::iterator_category() 进行分派,并对 std::random_access_iteratorstd::forward_iterator 使用不同的算法。

C++20 添加了概念和 requires 语句,这完全消除了在约束包括 AA && BAA || B(对于某些 AB)在概念的定义中明确或隐式地使用标签分发的需要。以下代码以 std::advance 为例。

namespace std
{
    // Input iterator overload
    constexpr void __advance(auto& __it, auto __n)
    {
        // If __n is negative, infinite loop
        // Undefined behavior as no observable side effects
        while (__n != 0)
        {
            --__n;
            ++__it;
        }
    }
    // Bidirectional iterator overload
    // Concept-constrained overload preferred so no problem
    constexpr void __advance(std::bidirectional_iterator auto& __it, auto __n)
    {
        if (__n < 0)
        {
            while (__n != 0)
            {
                ++__n;
                --__it;
            }
        }
        else
        {
            while (__n != 0)
            {
                --__n;
                ++__it;
            }
        }
    }
    // Random access iterator overload
    // std::random_access_iterator subsumes std::bidirectional_iterator so no problem
    constexpr void __advance(std::random_access_iterator auto& __it, auto __n)
    {
        __it += move(__n);
    }
    
    template <class _Ip, class _Np>
    constexpr void advance(_Ip& __it, _Np __n)
    {
        __advance(__it, move(__n));
    }
}
华夏公益教科书