Consider this code:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : Base {
public:
using Base::foo;
};
Now Derived
has a public method foo and it can be called
Derived d;
d.foo(2); // compiles (as it should)
However, I cannot do anything if I use the method through a pointer:
Derived d;
(d.*&Derived::foo)(2); // does not compile because `Derived::foo` expects a pointer to `Base` and `Derived` cannot be casted to its private base class (without a C-style cast).
Is there any logical explanation for this behaviour or perhaps it is an oversight in the standard?
->*
on a Derived
doesn't work with a Base::
member function pointer unless the private Base
in Derived
is accessible to you (e.g. within a member function of Derived
or in a function declared as friend of Derived
).Derived*
to Base*
as well as member function pointers of those types, even though Base
is not accessible (this would be illegal for any c++-style cast), e.g.:
Base* b = (Base*)&d;
would be legal in your example.Base::
member function pointer9.9 The using
declaration (emphasis mine)
12 [Note 5: For the purpose of forming a set of candidates during overload resolution, the functions named by a using-declaration in a derived class are treated as though they were direct members of the derived class. In particular, the implicit object parameter is treated as if it were a reference to the derived class rather than to the base class ([over.match.funcs]). This has no effect on the type of the function, and in all other respects the function remains part of the base class. — end note]
So the using
-declaration will not create a version of foo
for Derived
, but the compiler needs to pretend like it would be a Derived::
member for the purpose of overload resolution.
So the relevant bit in this case is This has no effect on the type of the function - i.e. you'll still get a function-pointer for Base::
if you take the address of foo
.
Note: The only exception to this is for constructors
->*
with the Base::
member pointer7.6.4 Pointer-to-member operators (emphasis mine)
3 The binary operator
->*
binds its second operand, which shall be of type “pointer to member of T” to its first operand, which shall be of type “pointer to U” where U is either T or a class of which T is an unambiguous and accessible base class. The expressionE1->*E2
is converted into the equivalent form(*(E1)).*E2
.
The problem here is that at the point you use the foo
pointer Base
is not accessible, so the call doesn't work.
Member functions are actually required to be castable to any derived type, as long as a few criteria are met:
7.3.13 Pointer-to-member conversions (emphasis mine)
2A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a complete class derived ([class.derived]) from B. If B is an inaccessible ([class.access]), ambiguous ([class.member.lookup]), or virtual ([class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed.
[...]
Given that Base
is neither ambiguous or virtual like in your example, the only problem we need to focus on is the accessibility part.
Or do we?
There's actually a small loophole in the standard that we can use:
7.6.3 Explicit type conversion (cast notation) (emphasis mine)
4 The conversions performed by
- (4.1) a
const_cast
([expr.const.cast]),- (4.2) a
static_cast
([expr.static.cast]),- (4.3) a
static_cast
followed by aconst_cast
,- (4.4) a
reinterpret_cast
([expr.reinterpret.cast]), or- (4.5) a
reinterpret_cast
followed by aconst_cast
,can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply, with the exception that in performing a
static_cast
in the following situations the conversion is valid even if the base class is inaccessible:
- (4.6) a pointer to an object of derived class type or an lvalue or rvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- (4.7) a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- (4.8) a pointer to an object of an unambiguous non-virtual base class type, a glvalue of an unambiguous non-virtual base class type, or a pointer to member of an unambiguous non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
[...]
So while any C++-casting method (like static_cast
/ reinterpret_cast
, etc...) doesn't allow the conversion from Base::*
to Derived::*
, a c-style cast is allowed to perform it, even when the base-class is inaccessible.
e.g.:
int main() {
Derived d;
auto fn = &Derived::foo;
// cast to base (only legal with c-style cast)
// Base* b = static_cast<Base*>(&d); // not legal
// Base* b = reinterpret_cast<Base*>(&d); // not legal
Base* b = (Base*)&d; // legal
(b->*fn)(12);
// cast member function pointer to derived
// (also only legal with c-style cast)
using MemFn = int (Derived::*)(int) const;
// auto fnD = static_cast<MemFn>(fn); // not legal
// auto fnD = reinterpret_cast<MemFn>(fn); // not legal
auto fnD = (MemFn)fn; // legal
(d.*fnD)(12);
// or as a one liner (provided by @KamilCuk in the comments):
// slightly hard to read, but still legal c++:
(d.*((int(decltype(d)::*)(int))&decltype(d)::foo))(12); // legal
}
godbolt example
is valid c++.
So just cast the Derived
to Base
or the member function pointer to one that's bound to Derived
.
There's even an example in the standard that does exactly that: 11.8.3 Accessibility of base classes and base class members (3)
&Derived::foo
return a Derived::*
memfn pointer?Because the standard says so. I don't know why they decided this way, but i can speculate what the potential reasons might be:
You can check which most-derived class implemented a given member function. This would break if &Derived::foo
would return a Derived::*
pointer. (This can e.g. be used with CRTP to check if a given Derived
class has provided a new definition for a given member of Base
)
e.g.:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : private Base {
public:
using Base::foo;
};
template<class T>
struct implementing_class_helper;
template<class T, class R>
struct implementing_class_helper<R T::*> {
typedef T type;
};
template<class T>
struct implementing_class : implementing_class_helper<typename std::remove_cv<T>::type> {
};
template<class T>
using implementing_class_t = implementing_class<T>;
int main() {
static_assert(std::is_same_v<
typename implementing_class<decltype(&Derived::foo)>::type,
Base
>, "Shenanigans!");
}
If a stub function would be created, e.g.:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : Base {
public:
// pretending using Base::foo; would result in this:
int foo(int x) { return Bar::foo(x); }
};
The compiler would now have a problem, since Base::foo
and Derived::foo
are different functions, but still need to compare equal to Base::foo
, since that's the actual implementation.
So the compiler would need to know all classes that contain using Base::foo;
in all compilation units and make sure whenever you compare their ::foo
with Base::foo
that the result is true
. And that sounds like a lot of implementation work for a strange edge-case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With