Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The non-type template parameter of pointer to overloaded member functions with inheritance

Background

Consider the following code:

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's 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> {};

The fragment struct<B, &B::g> {} doesn't compile because

  1. The type of &B::g is int (A::*)(), rather than int (B::*)();
  2. Though an implicit conversion from int (A::*)() to int (B::*)() is legal and viable, the C++ standard forbids (!!) any conversion when doing the template arguments substitution for the point-to-member-function template parameter. (Strictly speaking, a conversion to nullptr_t is allowed.)

As a consequence, F<B, &B::g> cannot match the exact definition of F and it fails to compile. This is sad because class A may be the implementation detail we don't want to bother with.

Workaround

A naive hack would be

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> {};

so far so good.

Problem

The above hack doesn't work with the overloaded class methods. Usually, the overloads can be resolved by static_cast, but this requires we know the exact type of &B::f -- a nice chick-and-egg situation.

// 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> {};

Something like G<decltype(static_cast<int(A::*)()>(&B::f)), &B::f> {}; is terrible.

To wrap up, the problem is how to correctly select a particular overload and avoid mentioning the base class A when &B::f is actually &A::f.

Any thoughts?

like image 536
subi Avatar asked Jun 25 '14 09:06

subi


1 Answers

I found a workaround fitting my requirements. Hoping it be helpful for anyone in need. I got stuck for days...

The idea is using a function template to match a particular overload, and then pass the correct type to G. A layer of indirection always saves the world.

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 is used without the awareness of A and selects the correct overload, cool.

Now the new problem is that I found G<decltype(forward_pmf_with_signature(&B::f)), &B::f> is redundant and error-prone. It's trivial to use a macro USE_G(&B::f) to simply the code, but it seems to me that it's not easy, or even possible to do the simplification in a semantic way.

like image 132
subi Avatar answered Dec 08 '22 01:12

subi