Проблема с подходом Jarod42 заключается в том, что вы меняете то, как выглядит разрешение перегрузки - как только вы сделаете все шаблоном, тогда все станет точным совпадением, и вы больше не сможете различать несколько жизнеспособных кандидатов:
struct A { void DoSomething(int); };
struct B { void DoSomething(double); };
SomeClass<A, B>().DoSomething(42); // error ambiguous
Единственный способ сохранить разрешение перегрузки - использовать наследование.
Ключ к завершению sh того, что ecatmur запущено. Но как выглядит HasDoSomething
? Подход в ссылке работает только в том случае, если существует один не перегруженный шаблон. Но мы можем сделать лучше. Мы можем использовать тот же механизм, чтобы определить, существует ли DoSomething
, для которого требуется using
для начала: имена из разных областей не перегружаются.
Итак, мы вводим новый базовый класс который имеет DoSomething
, который никогда не будет выбран по-настоящему - и мы делаем это, создавая наш собственный явный тип тега, который мы единственные, кто когда-либо будет создавать. Из-за отсутствия лучшего имени, я назову его в честь моей собаки, которая является Вестом ie:
struct westie_tag { explicit westie_tag() = default; };
inline constexpr westie_tag westie{};
template <typename T> struct Fallback { void DoSomething(westie_tag, ...); };
И сделаю это variadi c для хорошей меры, просто чтобы сделать это как минимум. Но это не имеет значения. Теперь, если мы введем новый тип, такой как:
template <typename T> struct Hybrid : Fallback<T>, T { };
Тогда мы можем вызвать DoSomething()
на гибриде именно тогда, когда T
делает не с перегрузкой DoSomething
- любого вида. Это:
template <typename T, typename=void>
struct HasDoSomething : std::true_type { };
template <typename T>
struct HasDoSomething<T, std::void_t<decltype(std::declval<Hybrid<T>>().DoSomething(westie))>>
: std::false_type
{ };
Обратите внимание, что обычно в этих чертах основной является false
, а специализация true
- здесь все наоборот. Ключевое различие между этим ответом и ответом ecatmur заключается в том, что резервная перегрузка все равно должна быть каким-то образом вызываемой - и использовать эту возможность для ее проверки - просто она не будет фактически вызываемой для любого типа, который пользователь будет фактически использовать.
Проверка таким образом позволяет нам правильно определить, что:
struct C {
void DoSomething(int);
void DoSomething(int, int);
};
действительно удовлетворяет HasDoSomething
.
И затем мы используем тот же метод, который показал ecatmur:
template <typename T>
using pick_base = std::conditional_t<
HasDoSomething<T>::value,
T,
Fallback<T>>;
template<typename... Bases>
class SomeClass : public Fallback<Bases>..., public Bases...
{
public:
using pick_base<Bases>::DoSomething...;
void DoSomething();
};
И это работает независимо от того, как выглядят все перегрузки Bases
DoSomething
, и правильно выполняет разрешение перегрузки в первом упомянутом мной случае.
Демо