Conversion from Derived** to Base**

Conversion from Derived** to Base**

It's basically the same reason why a bowl of bananas is not a bowl of fruits. If a bowl of bananas were a bowl of fruits, you could put an apple into the bowl, and it would no longer be a bowl of bananas.

As long as you only inspect the bowl, the conversion is harmless. But as soon as you start modifying it, the conversion becomes unsafe. This is the key point to bear in mind. (This is the precise reason why the immutable Scala collections actually allow the conversion, but the mutable collections prohibit it.)

Same with your example. If there was a conversion from Derived** to Base**, you could put a pointer to an apple were the type system promised only a pointer to a banana could exist. Boom!

Converting from Derived* to Base*&

An object of type Child* cannot be bound to a Parent*& for exactly the same reason that a Child** cannot be converted to a Parent**. Allowing it would allow the programmer (intentionally or not) to break type safety without a cast.

class Animal {};

class DangerousShark : public Animal {};

class CuteKitten : public Animal {};

void f(Animal*& animalPtrRef, Animal* anotherAnimalPtr)
{
animalPtrRef = anotherAnimalPtr;
}

void g()
{
DangerousShark myPet;
CuteKitten* harmlessPetPtr;

f(harmlessPetPtr, &myPet); // Fortunately, an illegal function call.
}

Edit

I think that some of the confusion arises because of the loose use of the words 'convert' and 'conversion'.

References can't be rebound, unlike objects which can be reassigned, so in the context of references when we speak of conversion we can only be concerned about initializing a new reference.

References are always bound to an object, and from the OP's question it was clear that he is aiming to get a reference that is a direct bind to an existing object. This is only allowed if the object used to initialize the reference is reference-compatible with the type of the reference. Essentially, this is only if the types are the same, or the type of the object is derived from the type of the reference and the reference type is at least as cv-qualified as the initializing object. In particular, pointers to different types are not reference-compatible, regardless of the relationship of the pointed-to types.

In other cases, a reference can be initialized with something that can be converted to the reference type. In these cases, though, the reference must be const and not volatile and the conversion will create a temporary and the reference will be bound to this temporary and not the original object. As pointed out, this is not suitable for the requirements of OP's motivating example.

In summary, a Child can be bound directly to a Parent& but a Child* cannot be directly bound to a Parent*&. A Parent* const& can be initialized with a Child*, but the reference will actually bind to a temporary Parent* object copy-initialized from the Child* object.

Why would the conversion between derived* to base* fails with private inheritance?

$11.2/4 states-

A base class B of N is accessible at R, if

  • an invented public member of B would be a public member of N, or
  • R occurs in a member or friend of class N, and an invented public
    member of B would be a private or
    protected member of N, or
  • R occurs in a member or friend of a class P derived from N, and an
    invented public member of B would be a
    private or protected member of P, or
  • there exists a class S such that B is a base class of S accessible at R
    and S is a base class of N accessible
    at R."

Here 'B' is 'Base', 'N' is 'Derived' and 'R' is main.

  1. Consider the 2nd bullet- 'R occurs in a member or friend of a class N,...'. This clause does not apply as 'R'(main) is neither a member nor friend of 'N'(Derived)

  2. Consider the 3rd bullet- 'R occurs in a member or friend of a class P....'. This claus also does not apply for the same reasons as above

  3. Consider the 4th bullet- Once again this clause does not apply

Thus we can conclude that 'Base' is not an accessible class of 'Derived'.

$11.2/5 states -

If a base class is
accessible, one can implicitly convert
a pointer to a derived class to a
pointer to that base class (4.10,
4.11). [ Note: it follows that members and friends of a class X can
implicitly convert an X* to a pointer
to a private or protected immediate
base class of X. —end note ]

Since Base is not an accessible class of Derived when accessed in main, the Standard conversion from Derived class to Base class is ill-formed. Hence the error.

EDIT 2:

Study the error messages of some popular compilers and that should help you get a better understanding. Note how the word 'inaccessible' pops up so frequently and consistently across all the error messages

The references are from the draft standard N3000. I am yet to download the latest draft :)

GCC prog.cpp: In function ‘int
main()’: prog.cpp:27: error: ‘base’ is
an inaccessible base of ‘derived’

Comeau Online "ComeauTest.c", line 26:
error: conversion to inaccessible base
class "base" is not
allowed
ptr = new derived;

VS2010 error C2243: 'type cast' :
conversion from 'derived *' to 'base
*' exists, but is inaccessible

Converting Derived** to Base** and Derived* to Base*

nasty_function(d); // Ooops, now *d points to a Base. What would happen now?

No, it doesn't. It points to a Derived. The function simply changed the Base subobject in the existing Derived object. Consider:

#include <cassert>

struct Base {
Base(int x) : x(x) {}
int x;
};
struct Derived : Base {
Derived(int x, int y) : Base(x), y(y) {}
int y;
};

int main(int argc, char **argv)
{
Derived d(1,2); // seriously, WTF is it with people and new?
// You don't need new to use pointers
// Stop it already
assert(d.x == 1);
assert(d.y == 2);
nasty_function(&d);
assert(d.x == 3);
assert(d.y == 2);
}

d doesn't magically become a Base, does it? It's still a Derived, but the Base part of it changed.


In pictures :)

This is what Base and Derived objects look like:

Layouts

When we have two levels of indirection it doesn't work because the things being assigned are pointers:

Assigning pointers - type mismatch

Notice how neither of the Base or Derived objects in question are attempted to be changed: only the middle pointer is.

But, when you only have one level of indirection, the code modifies the object itself, in a way that the object allows (it can forbid it by making private, hiding, or deleting the assignment operator from a Base):

Assigning with only one level of indirection

Notice how no pointers are changed here. This is just like any other operation that changes part of an object, like d.y = 42;.

Pointer-to-Member invalid conversion from ‘Derived X::*’ to ‘Base X::*’

Base X::*

means a pointer to a member of X having type Base.

It is not the same as

Base*

There are no conversion from

Base*

to

Base X::*  

and consequently no conversion from

Derived*

to

Base X::*  

Likewise there are no conversion between Base X::* and Derived X::*

Example:

#include <iostream>
using namespace std;

class Base
{
};

class Derived : public Base
{
};

class X {
public:
Derived field1;
Base field2;
};

int main() {
Base X::* ptr1 = &X::field1; // Derived X::* to Base X::* OK ?
Derived X::* ptr2 = &X::field2; // Base X::* to Derived X::* OK ?

return 0;
}

This will result in

prog.cpp:20:28: error: invalid conversion from 'Derived X::*' to 'Base X::*' [-fpermissive]
Base X::* ptr1 = &X::field1;
^
prog.cpp:21:31: error: invalid conversion from 'Base X::*' to 'Derived X::*' [-fpermissive]
Derived X::* ptr2 = &X::field2;

So in order to compile, it needs to be:

int main() {
Derived X::* ptr1 = &X::field1;
Base X::* ptr2 = &X::field2;

return 0;
}

Below is an example of how pointer-to-member can be used:

#include <iostream>
#include <vector>
using namespace std;

class Base
{
public:
Base(int g1) : g(g1) {}
int g;
};

class Derived : public Base
{
public:
Derived(int d) : Base(d) {}
};

class X {
public:
X(int f1, int f2) : field1(f1), field2(f2) {}
Derived field1;
Derived field2;
};

void foo(vector<X>& vx, Derived X::*d)
{
cout << "foo" << endl;
for (auto& x : vx)
{
cout << (x.*d).g << endl;
}
}

int main() {
vector<X> vx {{5, 10}, {50, 100}};
foo(vx, &X::field1); // Print field1.g of all elements in vector vx
foo(vx, &X::field2); // Print field2.g of all elements in vector vx

return 0;
}

This will output:

foo
5
50
foo
10
100

Conversion from Derived** to Base*const*

You're confusing two cases:

  • The addition of const
  • Upcasts

While formally (in computer science theory) both of these deal with subclassing, the reality is that the C++ rules for these are different, because the representation of const T and T are guaranteed to be the same, while the representations of Base* and Derived* often differ by an offset (but may be radically different when virtual inheritance is involved).

In 3.9.3, the Standard declares that

The cv-qualified or cv-unqualified versions of a type are distinct types; however, they shall have the same representation and alignment requirements

Given:

struct Base {};
struct Derived : Base {};
Derived* pd = nullptr;
Base* pb = pd;

const can indeed by added in the way you suggest.

Base const* const* const cpcpcb = &pb;
Base* const* pcpb = &pb; // legal, pointer can't be changed
Base const* * ppcb = &pb; // illegal, one could try to rebind the pointer
// to a truly const object, then
// use pb to mutate the const object

But there is no is-a relationship between Derived* and Base*. A conversion exists, but the Derived* variable does not necessarily contain the address of a Base object (the Base subobject within the Derived object may have a different address). And therefore both the line you're complaining about, and the line your question assumed was valid, are illegal:

Base const* const* const cpcpcd = &pd; // error, there's no address of a Base
// to be found in pd
Base* const* pcpd = &pd; // error: again, there's no address of a Base
// stored in pd

Formally, the Standard describes this in 4.10:

A prvalue of type "pointer to cv D”, where D is a class type, can be converted to a prvalue of type "pointer to cv B", where B is a base class of D. If B is an inaccessible or ambiguous base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.

The result of the conversion is a prvalue, it doesn't have an address, and you can't create a pointer to it.

Cast derived class to base

This is a standard derived-to-base pointer conversion. The rules are that a pointer to D with some const/volatile qualifications can be converted to a pointer to B with the same qualifiers if B is a base class of D.

The standard conversions are implicit conversions with built-in meanings and are separate concepts to things like static_cast or C-style casts.

Generally it's best to rely on implicit conversions when you can. Explicit conversions add more code noise and may hide some maintenance mistakes.

Automatic conversion from derived class to base class' member variable's type

When compiler sees d == B{2}, it first creates a list of operator== overloads that it's able to find, and then performs overload resolution on them.

As the link explains, the overload list contains:

  • Member operator==s of the first operand, if any.
  • Non-member operators==s found by unqualified lookup, if any.
  • Built-in operator==s, if your operands can be converted to built-in types.

There's no mention of examining conversion operators of the first operand, so your operator== doesn't get found.

The solution is to make the operator== non-member (possibly define it as a friend). This works:

friend bool operator==(const B &a, const B &b) {return a.x == b.x;}

Starting from C++20, the rules of comparison operators got relaxed: the compiler will look for member operator== in the second operand as well, and will happily call non-member operator== with arguments in a wrong order.

So a == b and b == a are now equivalent to a degree, except that:

  • An operator== called in this new manner must return bool.
  • If given choice, the compiler will prefer the old ways of calling operator== over calling the member operator== of the rhs, which is in turn preferred over calling a non-member operator== with a wrong argument order.

C2440 when assigning `Derived**` to `Base**`

The answer lies in what a cast to base pointer actually does in C++. Consider the following simple example:

struct A { std::uint32_t a; };
struct B { std::uint32_t b; };
struct D : A, B { std::uint32_t d; };

Here D has two bases, A and B. But only one of them can live at the beginning of D (lets say it is A), so when you convert the D to the other one (B), the value of the pointer needs to change, to point at some point in the middle of D.

To visualize this, consider how an object of type D looks like in your memory:

0 1 2 3 4 5 6 7 8 9 A B
[ A::a ][ B::b ][ D::d ]
^ a D* would point here
^ a A* would point here
^ a B* must point here

Whilst D expects all three of these integers, when thinking of this object as an A, we only expect the first one, but the pointer is (numerically) the same, we only expect a shorter object. However, when thinking of it as an B, we need to point at the one integer in the middle.

When you do that to one pointer, the compiler will take care of this for you by changing the value of the pointer appropriately. However, when you do that to an array of pointers, the compiler would need to emit a loop over all elements of that and correct every single pointer - when in fact it cannot even know where the array ends (since the compiler only sees a pointer to its beginning)!

Therefore C++ disallows this kind of conversion.



Related Topics



Leave a reply



Submit