更多 C++ 惯用法/标签分发
外观
简化编写多个 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_iterator
和 std::forward_iterator
使用不同的算法。
C++20 添加了概念和 requires
语句,这完全消除了在约束包括 A
和 A && B
或 A
和 A || B
(对于某些 A
和 B
)在概念的定义中明确或隐式地使用标签分发的需要。以下代码以 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));
}
}