Фон
Рассмотрим следующий код:
struct A { // a class we want to hide from the client code
int f();
int f(char);
int g();
};
struct B : A {}; // the interface class
// client code:
//
// It natural to assume a member function of T should have
// a type of int (T::*)(), right?
template <typename T, int (T::*)()> struct F {};
// compiles, of course
F<A, &A::g> {};
// doesn't compile, because &B::g is not of type `int (B::*)()`, and no
// conversion is allowed here
F<B, &B::g> {};
// compiles, because &B::g is actually &A::g
//
// but this is not I want because the class hierarchy may be deep and it's
// not easy to know what A is.
F<A, &B::g> {};
Фрагмент struct<B, &B::g> {}
не компилируется, потому что
- Тип
&B::g
-int (A::*)()
, а неint (B::*)()
; - Хотя неявное преобразование из
int (A::*)()
вint (B::*)()
является законным и жизнеспособным, стандарт С++ запрещает (!!) любое преобразование при замене аргументов шаблона для параметра шаблона функции-члена. (Строго говоря, допускается преобразование вnullptr_t
.)
Как следствие, F<B, &B::g>
не может соответствовать точному определению F
и не скомпилируется. Это печально, потому что class A
может быть деталью реализации, с которой мы не хотим беспокоиться.
Обход
Наивный взлом будет
template <typename T, T val> struct G {};
// at least compiles, and don't have to be aware of A
G<decltype(&B::g), &B::g> {};
пока что так хорошо.
Проблема
Вышеупомянутый хак не работает с перегруженными методами класса. Обычно перегрузки могут быть разрешены static_cast, но для этого требуется, чтобы мы знали точный тип & B:: f - хорошая ситуация с курицей и яйцом.
// won't compile since &B::f is ambiguous
G<decltype(&B::f), &B::f> {};
// won't compile just like F<B, &B::g>
G<decltype(static_cast<int(B::*)()>(&B::f)), &B::f> {};
// compiles, but require the knowledge of the type of &B::f
G<decltype(static_cast<int(A::*)()>(&B::f)), &B::f> {};
Что-то вроде G<decltype(static_cast<int(A::*)()>(&B::f)), &B::f> {};
ужасно.
Чтобы завершить работу, проблема заключается в том, как правильно выбрать конкретную перегрузку и не упоминать базовый класс A
, когда &B::f
на самом деле &A::f
.
Любые мысли?