Во-первых, вам нужна черта, чтобы увидеть, что-то похоже на A
. Вы не можете просто использовать is_base_of
здесь, так как вы не знаете , от которого A
будет наследоваться. Нам нужно использовать дополнительную косвенность:
template <typename T>
auto is_A_impl(A<T> const&) -> std::true_type;
auto is_A_impl(...) -> std::false_type;
template <typename T>
using is_A = decltype(is_A_impl(std::declval<T>()));
Теперь мы можем использовать эту черту для записи трех наших перегрузок: и A
, только слева A
и только справа A
:
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__), int> = 0
// both A
template <typename T, typename U, REQUIRES(is_A<T>() && is_A<U>())
void fn(T const&, U const&);
// left A
template <typename T, typename U, REQUIRES(is_A<T>() && !is_A<U>())
void fn(T const&, U const&);
// right A
template <typename T, typename U, REQUIRES(!is_A<T>() && is_A<U>())
void fn(T const&, U const&);
Обратите внимание, что я просто беру здесь T
и U
, мы не обязательно хотим унывать и терять информацию.
<ч />
Одна из приятных особенностей концепций, появившихся в C ++ 20, заключается в том, насколько проще написать это. Обе черты, которые теперь становятся концепцией:
template <typename T> void is_A_impl(A<T> const&);
template <typename T>
concept ALike = requires(T const& t) { is_A_impl(t); }
И три перегрузки:
// both A
template <ALike T, ALike U>
void fn(T const&, U const&);
// left A
template <ALike T, typename U>
void fn(T const&, U const&);
// right A
template <typename T, ALike U>
void fn(T const&, U const&);
В правилах языка уже предусмотрено, что перегрузка «оба А» предпочтительна, когда она жизнеспособна. Хорошая вещь.