13

With g++ 5.4, this

struct B {
    void f() {}
}; 

struct D : public B {
    void g() {}
};

template <class T>
void foo(void (T::*)(), void (T::*)())
{}

int main()
{
    foo(&D::f, &D::g);
}

fails due to "deduced conflicting types for parameter ‘T’ (‘B’ and ‘D’)". Why isn't T deduced as D, being an exact match?

fizzer
  • 13,551
  • 9
  • 39
  • 61
  • 3
    Maybe because there's no `D::f`? Only `B::f` exists, so `&D::f`'s type is `B::*`? – geza Nov 09 '17 at 12:07

2 Answers2

13

The type of &D::f would be void ( B::* )(void)

static_assert(::std::is_same<void ( B::* )(void), decltype(&D::f)>::value, "");
static_assert(::std::is_same<void ( D::* )(void), decltype(&D::f)>::value, ""); // error
static_assert(::std::is_same<void ( D::* )(void), decltype(&D::g)>::value, "");

The rationale behind this is that otherwise you won't be able to assign a value of &D::f to a variable of void ( B::* )(void) type without a cast even though f is a member of B or compare &D::f == &B::f.

As a workaround you can perform a static_cast:

foo(static_cast<void (D::*)(void)>(&D::f), &D::g);
user7860670
  • 35,849
  • 4
  • 58
  • 84
  • For me, it is hard to accept the rationale. If I'd want to assign `&D::f` to a `B::*`, then I'd refer it as `&B::f`. This behavior of C++ is unexpected, I've met this phenomenon several times in the past. Why does the type of `&D::f` change whether there is an actual `f` in `D`? I understand the working behind this, but cannot accept it. As a user of `B` & `D`, I don't care, whether the person who created `B` or `D` added an `f` to `D` or not. – geza Nov 09 '17 at 12:27
  • 4
    an easier workaround would be `foo(&D::f, &D::g)` – CAF Nov 09 '17 at 14:08
  • 1
    @geza But this way `&D::f == &B::f` is correctly evaluated to `true` which makes perfect sense to me because `f` refers to the same function in base class. – user7860670 Nov 09 '17 at 18:11
12

In addition to VTT's excellent demonstration. The standard text in question, I believe, is at [expr.unary.op]/3, emphasis mine:

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static or variant member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C​::​m.

The qualified id you used is D::f, but it names a member function of B (I can bring up the lookup rules if you want). So the class type C in the above paragraph, is B. The type therefore resolves to void ( B::* )(void).

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 4
    I was unconvinced initially, but I see from [class.mem] - "The member-specification in a class definition declares the full set of members of the class; no member can be added elsewhere." - that f is unequivocally a member of B and not a member of D, despite the D:: nested-name-specifier. – fizzer Nov 09 '17 at 12:57