Параметр непигового шаблона указателя на перегруженные функции-члены с наследованием

Фон

Рассмотрим следующий код:

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.

Любые мысли?

Ответ 1

Я нашел обходное решение, соответствующее моим требованиям. Надеясь, что это будет полезно для всех, кто в этом нуждается. Я застрял в течение нескольких дней...

Идея заключается в использовании шаблона функции для соответствия конкретной перегрузке, а затем передать правильный тип G. Слой косвенности всегда сохраняет мир.

template <typename T>
auto forward_pmf_with_signature( int(T::*pmf)() ) -> decltype(pmf);

G<decltype(forward_pmf_with_signature(&B::f)), &B::f> {}; // works

G используется без осознания A и выбирает правильную перегрузку, охлаждая.

Теперь новая проблема заключается в том, что я обнаружил, что G<decltype(forward_pmf_with_signature(&B::f)), &B::f> является избыточным и подверженным ошибкам. Тривиально использовать макрос USE_G(&B::f) просто для кода, но мне кажется, что это непросто или даже возможно сделать упрощение семантическим способом.