virtual tables and inheritance c++ - c++

i have a question regarding calling a function with virtual methods and multiple inheritence.
i have the following code:
#include<iostream>
using namespace std;
class A{};
class B:public A{};
class C:public B{};
class AA:public A{};
struct X
{
void f(A*) { std::cout<< "X::f(A*)\n";}
virtual void f(B*) { std::cout<< "X::f(B*)\n";}
void f(C*) { std::cout<< "X::f(C*)\n";}
virtual void f(C*) const { std::cout<< "const X::f(C*)\n";}
};
struct Y:public X {
virtual void f(B*) { std::cout<< "Y::f(B*)\n";}
void f(A*) { std::cout<< "Y::f(A*)\n";}
virtual void f(C*) const { std::cout<< "const Y::f(C*)\n";}
};
int main() {
Y* y=new Y();
y->f(new C);
}
I can't understand why this turns ambiguous and there are 2 candidates:
Y::f(B*)
Y::f(C*)

For an overloading function to be selected, it has to be the "best" at accepting each individual argument, including the implicit argument that becomes this. Best is defined in terms of the least conversions needed to convert the argument (in the caller) to the parameter (in the callee).
virtual void f(C*) const agrees perfectly with an argument of type C*, but the const qualifier at the end requires that this be converted from a non-const Y* to a Y const *. This is what the compiler is complaining about. If you cast
static_cast< Y const * >( y )->f(new C);
The problem goes away (although this isn't immediately illustrative since the extra qualification disqualifies the other overloads).
Note that all the overloads in X aren't even checked. The name resolution which finds all the overloads to be considered starts at the derived class and proceeds up the inheritance branches until it finds a matching name, and then it stops. To merge functions from multiple classes into one overload set, use a using declaration inside Y, using X::f;.
The actual solution to this problem is probably to introduce more matching overloads without const qualifiers at the end, so the const qualification of the calling pointer doesn't play such an unintuitive role.

Your overload for C is the only const member function.
y is non const and all overloads of f are acceptable, hence the call is ambiguous.
Ways to resolve the ambiguity:
Add a non const overload for C
Make y const
Make the overloads for A and B const

void f(C*) { std::cout << "X::f(C*)\n"; } in base class
is not visible due to name hiding.
use
using X::f;
in derived class, and it works as expected.

Related

I have Question about class overriding in C++

When I study about override keyword, I found some strange thing like below code.
#include <iostream>
template <class T>
class A
{
public:
virtual void some_function(const T a)
{
std::cout<<__PRETTY_FUNCTION__<<std::endl;
std::cout<<"Base"<<std::endl;
}
};
class Derived : public A<int*>
{
public:
virtual void some_function(const int* a)
{
std::cout<<__PRETTY_FUNCTION__<<std::endl;
std::cout<<"Derived"<<std::endl;
}
};
int main()
{
A<int*>* p = new Derived;
p->some_function(nullptr);
delete p;
}
When I first saw that code, I expected "Derived" to be called.
But above code print result like below.
void A<T>::some_function(T) [with T = int*]
Base
But when I removed const keyword in the some_function that placed in Derived class,
class Derived : public A<int*>
{
public:
virtual void some_function(int* a)
{
std::cout<<__PRETTY_FUNCTION__<<std::endl;
std::cout<<"Derived"<<std::endl;
}
};
It print "Derived".
Can you tell me why this is happening?
The function prototypes are not the same. For T=int* const T a: a is a const pointer to an int, while const int *a is a pointer to an const int. For one the pointer is const, for the other the int is const.
int * const a would be the same as const T a, or you can make T=const int*.
https://godbolt.org/z/WEaEoGh58
Also important to note: When you derive from a class you must declare a virtual destructor.
A hint: when you use the keyword override on the derived function, you will get an error that you are not overriding the function: https://godbolt.org/z/o87GMrhsd
At
const T a
const qualifier is interpreted as top-level cv-qualifier for T, so it is processed
T const a
thus if int* is set to T, parameter type becomes
int* const a
So, your Derived's function signature (it is called second level const qualifier)
const int* a
is different from Base's one. As a result, your Derived virtual function is not called.
In addition, C++ has the rule of "ignore top-level cv-qualifier from function signature". So, your Base's signature
int* const
and if you give the type of
int*
into Derived's signature, these two types signature are interpreted to be equivalent. As a result, your Derived virtual function is called correctly.
The following is a back-ground for your help. The type of
T*
has two component T and pointer and then
T* const
is called top-level cv-qualifier which is applied to *, and then
T const *
is called second level cv-qualifier which is applied to T (equal to const T*). C++ ignore top-level cv-qualifier from signature. On the other hand, second level qualifier is included to signature. Therefore for example,
void f(int*) {}
void f(int* const){}
is failed to compile because of same signature by top-level cv-qualifier ignoring. In contrast,
void f(int*) {}
void f(int const * ){}
is compiled successfully by the difference of second level cv-qualifiers.
This is important to understand the behavior of virtual function calls. The reason is that the signature of derived virtual function and base's one must be identical to call it correctly.

Allow implicit cast operator for const reference only

Is there a way to make a custom cast operator only available (or only implicit) if the object is const?
Example:
class Foo;
class Bar;
class MyClass {
public:
operator Foo() const;
operator Foo() = delete; // this doesn't seem to have any effect
// I also tried explicit operator Foo(); - no change
operator Bar();
};
As you can see, I'd like MyClass to be implicitly cast to Foo if it is const, but to Bar if it is not const. This is because a pair of overloaded functions exists:
void f(Foo x);
void f(Bar x);
And I'd like to be able to pass MyClass to f, so that the correct function is selected depending on whether it is const or not. This way however, I am getting an ambiguous call to overloaded function. Why?
int main() {
f(MyClass());
}
Important: I know that I can make this work easily by turning the cast operators into constructors, but unfortunately, Foo, Bar, and f cannot be modified. For context, this is an idea to solve my other problem: Is there a way to resolve this ambiguous implicit cast operator overload?
The best viable overload is chosen before its access and/or removal is checked. As there is no best viable overload with the original class definition it doesn't even get to that stage. That is, the ambiguity needs to be resolved for the overload resolution already.
Making one of the two conversion operators explicit does resolve the issue (with the test program there are still errors due to Bar being incomplete). Using a combination of explicit (and = deleteed although that is optional) conversions does yield a version which may be what is looked for:
#include <iostream>
class Foo {};
class Bar {};
class MyClass {
public:
explicit operator Foo() const& = delete;
explicit operator Foo() && = delete;
operator Foo()& { return Foo(); }
explicit operator Bar() const& = delete;
operator Bar() && { return Bar(); }
explicit operator Bar() & = delete;;
};
void f(Foo) { std::cout << "f(Foo)\n"; }
void f(Bar) { std::cout << "f(Bar)\n"; }
int main() {
f(MyClass());
MyClass x;
f(x);
}
I didn't manage to create a version also accepting MyClass const y; f(y);: making the const& conversion operator non-explicit (for either conversion) causes an ambiguity elsewhere.

`using` Only Some Overloads Of A Base Class

Consider a class b with two overloaded methods of foo:
struct b {
void foo(float) {}
void foo(const char *) {}
};
If I derive d privately from b, I can use using to expose b's foo:
struct d : private b {
using b::foo;
};
However, this exposes all overloads. Is there a way to expose only one of them (say, the float one)? For example, in the following, I'd like the last line to fail compilation:
d t;
t.foo(3.13); // d should have this overload
t.foo("hello"); // d shouldn't have this overload
I tried various ways of writing
using b::<i mean only void foo(float), dammit!>
but couldn't get any of them to compile.
Also, obviously it's possible to define in d just the required overload calling b's overload
struct d : private b {
void foo(float f) { b::foo(f); }
};
but the question is if it's possible to do this tersely with using only.
No, that is not possible. A using declaration, just like any other declaration, operates on names.
using b::foo; introduces the name foo into the declaration's containing scope, such that it refers to whatever b::foo refers to. The name b::foo refers to a "family" of overloaded functions, so after the using-declaration, the name foo refers to the same.
If you want to "publish" only some overloads, you have to do it using the trampoline functions you've shown:
struct d : private b {
void foo(float f) { b::foo(f); }
};
As mentioned by #Angew in his answer, an using declaration introduces names in a namespace.
Because of that, you can't pick only your preferred ones up, but you can still do the opposite and = delete the ones you don't want to expose:
struct B {
void f() { }
void f(int) { }
void f(int, char) { }
};
struct D: B {
using B::f;
void f(int) = delete;
};
int main() {
D d;
d.f();
d.f(0, 'c');
// this won't work
// d.f(0);
}
This is not exactly what you were looking for, but it is a workaround to obtain almost the same result.
It follows a comment made by #Yakk that is worth quoting in the answer:
Note that a deleted overload is not the same as not having one. If missing a different overload may be selected, while if deleted it may be instead selected and generate an error.
This is right, if the solution above works for the OP mostly depends on the real problem.
I cannot say that, but still this is a viable solution for some cases

Virtual method causes compilation error in Derived class

Consider the next code :
#include <iostream>
using namespace std;
class A
{
public:
virtual int f() {cout <<"A:: f()\n"; return 1;}
virtual void f(int) {cout <<"A:: f(int)\n";}
virtual void g() {cout <<"A::g()\n";}
};
class B3 : public A
{
public:
void f() {cout <<"B3::f ()\n";}
};
int main()
{
return 0;
}
It produces the following error :
..\main.cpp:17: error: conflicting return type specified for 'virtual void B3::f()'
..\main.cpp:9: error: overriding 'virtual int A::f()'
but why ? in the worst case I'd think I'd have an Hiding case , but instead I get compilation error regarding A's virtual int f() {cout <<"A:: f()\n"; return 1;}
thanks ,Ronen
Don't confuse overriding with hiding. You override virtuals.
Your class definition is equivalent to:
class B3 : public A
{
public:
virtual void f() {cout <<"B3::f ()\n";}
};
Once a function declared virtual, it will remain virtual for all classes deriving from your base class, regardless of whether you explicitly declare it virtual or not. Therefore you are trying to override a virtual function and changing its return type, which is illegal in C++. If the function were not virtual, you would simply be hiding the base class implementation, therefore changing the return type is valid. There would be no ambiguity since the compiler would know where to call the function from and what return type to expect.
However, consider having:
A* a;
....
a->f();
What would a-f() return? a is a pointer to A, but can point to an object of type B3. So it either returns an int or doesn't return anything. See the ambiguity here?
Instead, no polymorphism involved,
A a;
a.f();
will cal f from a, same as b3.f would call f from B3.
All in all, overriding base class functions implies keeping the same return type. If you want to create a new function with a different return type, change its signature (either its name or its parameters - or both).
Anyway, you shouldn't even be doing this... Why would you want to have a function with the same name and no parameters return different things? Wouldn't adding a separate function be more readable?
You'd have hiding if f() would have had a different parameter list, or not declared as virtual on the base class. In the former case, since overloading doesn't cross inheritance borders, A's f would have been hidden. But this is not the case, since you have f() on both classes, which only differ in return value. Return values covariance is the only difference allowed, and since it's not the case (void does not inherit from int), you get the error.

Why can't I call a templated method from a derived class instance in C++?

Please consider these types:
struct X
{
static std::string fullyQualifiedName();
};
struct A
{
template <class T> void foo()
{
return foo(T::fullyQualifiedName());
}
protected:
virtual void foo(const std::string& name) = 0;
};
struct B : public A
{
protected:
void foo(const std::string& name);
};
I have a pointer to an instance of B, I'm trying to invoke the templated version of foo like so:
b->foo< X >();
The compiler is complaining: 'X' : illegal use of this type as an expression.
On the other hand, this code's perfectly ok:
A* a = b;
a->foo< X >();
Hence my question.
The problem you are facing is called hiding. Basically the lookup rules in the language will start in the most derived type and work their way back up until it finds the appropriate symbol. In your case it will stop while looking at the B class as it finds void B::foo(const std::string& name). At that level, the only potential overload that it will consider is the one it is seeing.
To avoid hiding you can bring all the other overloads you can add a using declaration:
struct B : A{
using A::foo;
void foo( const std::string & name );
};
The difference is that lookup will work up the hierarchy until it finds the first overload, which is again at B level, but because of the using directive, it will also consider any overload available in A.
Alternatively, you can leave the symbol hidden and force dispatch to the exact class by qualifying the call (note that this has the side effect of disabling dynamic dispatch, which is not a problem here, but could be if the overload to use is virtual):
b->A::foo<X>();