更多 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));
}
}