0

I'm in an intro to C++ class and I'm trying to find out why I'm getting a "no matching function for call" error. And I've looked through other posts, but those mainly seem to be problems with the constructors themselves.

This is simplified snippets:

In Base Class - Ship

// Members: shipName, shipBuiltYear
Ship::Ship(){ //implementation }
Ship::Ship(string name, string year){ //implementation }
void Ship::set(string name, string year){ //implementation }

In Derived Class - PirateShip

// Members: numPirates
PirateShip::PirateShip() : Ship() { //implementation }
PirateShip::PirateShip(string name, string year, string pirates) : ship(name, year){ //implementation }
void PirateShip::set(int pirates){ //implementation }

In main

Ship *ships[2] = {new Ship(), new PirateShip()};
ships[0] -> set("Luvinia", "2020"); // using setter from base class
ships[1] -> set("Skylin", "2030"); // using setter from base class
ships[1] -> set(100); // using setter from derived class

Is the problem that you can't use the base class to set the PirateShip, then use the PirateShip to set it again?

Do I have to change:

void PirateShip::set(int pirates){ //implementation }

to:

void PirateShip::set(string name, string year, string pirates)
{ 
    Ship::set(name, year);
    numPirates = pirates; 
}

?

Or is there another way to do this?

Amai
  • 141
  • 6
  • 1
    If you have a pointer to `Ship`, you can only use the `Ship` interface. I think you can solve this particular situation by adding constructors to your classes. – molbdnilo Dec 03 '18 at 05:59
  • @molbdnilo I actually have constructors, I didn't think it was related to that so I didn't add it in my snippets, but I just editted it. So it doesn't seem to be related to that, unless I'm looking at it wrong. – Amai Dec 03 '18 at 06:04
  • I closed this as a duplicate (of many questions that deal with name hiding in a derived class), then reopened because I'm now not quite sure what the actual problem is. Chances are, it is that problem, but without a [mcve] it's hard to be sure. – n. m. could be an AI Dec 03 '18 at 06:10
  • The potential duplicates are [1](https://stackoverflow.com/questions/1628768/why-does-an-overridden-function-in-the-derived-class-hide-other-overloads-of-the) [2](https://stackoverflow.com/questions/3202234/overloaded-functions-are-hidden-in-derived-class) [3](https://stackoverflow.com/questions/16835897/overloading-base-class-method-in-derived-class) and more. – n. m. could be an AI Dec 03 '18 at 06:14
  • Right now, I'm thinking that it might not be possible to call a base class setter to set 2 of the 3 variables for the derived object. THEN call the derived class setter to set the last variable for the derived object. Maybe I have to combine it into one derived class setter. – Amai Dec 03 '18 at 06:15
  • When you call through a `Ship*` it only knows about the `Ship` part of th object. You can only call function signatures defined for `Ship`. Not subclass interfaces that differ. – Galik Dec 03 '18 at 06:16
  • @Galik But does the "new PirateShip()" part, not allow you to call the functions from PirateShip? If not, how would you go about doing that? – Amai Dec 03 '18 at 06:18
  • 1
    @Amai Create the objects first, set them up, then store their base pointers. That is, don't throw away the type information until you don't need it any more. – molbdnilo Dec 03 '18 at 06:20
  • @molbdnilo So from what I'm understanding, I either create and instantiate the object outside the pointer array, then place it into pointer. Shouldn't creating an empty PirateShip using PirateShip(), then setting it also work tho? – Amai Dec 03 '18 at 06:32
  • Please show a [mcve], otherwise you will be wasting a lot of your and others' time. – n. m. could be an AI Dec 03 '18 at 06:48
  • @Amai I mean. `Derived* p = new Derived; p->setup_derived(); Base* a[] = { p };`. – molbdnilo Dec 03 '18 at 07:35
  • Your design is broken, and telling you how to force the compiler to allow what you want would encourage bad design. Why not specify that the `Ship` class has an attribute that represents "crew size"? It can then provide member functions to set/get that value (which may also be either `virtual` or non-`virtual` depending on your goals). For a pirate ship, the crew size is - presumably - the number of pirates. That way, the `Ship` class provides the interface you need, and no need to work out if it is actually a pirate ship to set the number of pirates on board a pirate ship. – Peter Dec 03 '18 at 09:05

2 Answers2

1

There are two problems here.

First:

Ship *ships[2] = {new Ship(), new PirateShip()};

this is an array of pointers to the Ship part of two objects. The first is a Ship, the second a PirateShip. But you only have a pointer to the Ship part of the PirateShip, so can only (directly) interact with it.

If Ship has virtual methods, then you could use RTTI and dynamic_cast to query if a given Ship* is pointing to the Ship part of a PirateShip like this:

auto* pirate = dynamic_cast<PirateShip*>(some_ship);

if pirate is non-null, then some_ship pointed at an Ship piece of a PirateShip.

Note that using dynamic cast is code smell; it probably means you should either improve the base class interface, or not have a pointer to base class here.


The second part is if you want to be able to call Ship::set from PirateShip*, you need to add

using Ship::set;

to the body of PirateShip.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

If you know that an element is a specific type (and you must know that if you are calling with its specific parameter requirements) then you can cast to the appropriate type:

Ship* ships[2] = {new Ship(), new PirateShip()};
ships[0] -> set("Luvinia", "2020"); // using setter from base class
ships[1] -> set("Skylin", "2030"); // using setter from base class
dynamic_cast<PirateShip*>(ships[1]) -> set(100); // change to the correct interface

However I would question the design here. For polymorphism to work well, you should not need to know the exact types of the sub classes after creation.

Perhaps make the initialization call part of the constructor? Or configure the objects before adding them to the array?

ALSO

Using raw pointers to own objects is not considered good practice these days. I recommend a smart pointer:

std::unique_ptr<Ship> ships[2];

Also using built in arrays is similarly less than optimal in most cases. Consider a std::vector:

std::vector<std::unique_ptr<Ship>> ships;

Or a std::array if you want a fixed size:

std::array<std::unique_ptr<Ship>, 2> ships;
Galik
  • 47,303
  • 4
  • 80
  • 117
  • Even if I configure objects before adding it to the array, if I try to call the set function, it would still end up giving me errors would it not? And I'm not sure what you mean by "make the initialization call part of the constructor". I prefer sticking to raw pointers for now, because my professor has not taught us about smart pointers and he is kind of strict on grading by his own standards. – Amai Dec 03 '18 at 06:38