Проблема с подходом Jarod42 заключается в том, что вы меняете вид разрешения перегрузки - как только вы делаете все шаблоном, все становится точное совпадение, и вы больше не сможете различать несколько жизнеспособных кандидатов:
struct A { void DoSomething(int); };
struct B { void DoSomething(double); };
SomeClass<A, B>().DoSomething(42); // error ambiguous
Единственный способ сохранить разрешение перегрузки — использовать наследование.
Ключевым моментом является завершение того, что начал ecatmur. Но как выглядит HasDoSomething
? Подход в ссылке работает только при наличии одного, не перегруженного, не шаблонного. Но мы можем сделать лучше. Мы можем использовать тот же механизм, чтобы определить, существует ли DoSomething
, который требует using
для начала: имена из разных областей не перегружаются.
Итак, мы вводим новый базовый класс, который имеет DoSomething
, который никогда не будет по-настоящему выбран, и мы делаем это, создавая свой собственный явный тип тега, который мы единственные, кто когда-либо будет создавать. За неимением лучшего имени, я назову его в честь моей собаки Вести:
struct westie_tag { explicit westie_tag() = default; };
inline constexpr westie_tag westie{};
template <typename T> struct Fallback { void DoSomething(westie_tag, ...); };
И сделайте его вариативным для хорошей меры, просто чтобы сделать его как можно меньше. Но на самом деле это не имеет значения. Теперь, если мы введем новый тип, например:
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
, и правильно выполняет разрешение перегрузок в первом упомянутом мной случае.
Демо
person
Barry
schedule
06.04.2020
class SomeClass : public Wrapper<Bases>...
- person MikeMB   schedule 05.04.2020void DoSomething()
во вспомогательный класс и наследуйте его вместе сBases...
. - person Igor Tandetnik   schedule 05.04.2020