-2

I ran across this example while studying early binding vs late binding, and I'm realizing that I really have no idea how compiling really works.

Here, the output is "Instrument::play", even though it really should be "Wind::play", but this can be changed by making the function Instrument::play a virtual function.

I can clearly see that the object flute is a Wind object, not an Instrument object. Would I be wrong in assuming that the compiler would know this as well? I mean, I figure compilers would always type-check function parameters, so in this case, wouldn't it check to see if an appropriate Instrument object was used, see that it's a Wind object, and automatically select the Wind version of play()?

#include <iostream>

using namespace std;
enum note { middleC, Csharp, Eflat }; // Etc.

class Instrument {
public:
  void play(note) const {
    cout << "Instrument::play" << endl;
  }
};

class Wind : public Instrument {
public:
  
  void play(note) const {
    cout << "Wind::play" << endl;
  }
};

void tune(Instrument i) {
  i.play(middleC);
}


int main()
{
  int a = 1+1;
  Wind flute;
  tune(flute);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Mezzoforte
  • 59
  • 1
  • 6
  • 4
    polymorphic beahvior only works through pointers and references. – pm100 Mar 17 '23 at 18:40
  • Bottom line -- C++ is not Python or Java. – PaulMcKenzie Mar 17 '23 at 18:47
  • @Mezzoforte "*Would I be wrong in assuming that the compiler would know this as well?*" - Yes. "*wouldn't [the compiler] check to see if an appropriate Instrument object was used, see that it's a Wind object, and automatically select the Wind version of play()?*" - No. Polymorphic dispatch via `virtual` methods will handle that at runtime. However, you are **slicing** the `flute` object when passing it to `tune()` as an `Instrument` *by value*, so it wouldn't matter if `Instrument::play()` were `virtual` (which it should be). You need to pass it by reference/pointer instead to avoid slicing. – Remy Lebeau Mar 17 '23 at 18:55
  • @RemyLebeau So when the compiler checks that flute is a wind object, what exactly happens? I know about upcasting. Is that what happens? It gets upcasted into a instrument object, and thus instrument::play gets invoked? – Mezzoforte Mar 17 '23 at 19:00
  • C++ follows an ideology of only making a program pay for what it asks for, and runtime polymorphism has a (typically small) cost, so you have to ask for it with the `virtual` keyword if you really do want use it and are OK paying the performance penalty. – user4581301 Mar 17 '23 at 19:04
  • In this case there is no casting involved. You're passing the object by value, so a copy of the object is made. The receiving function only accepts an `Instrument`, so all of the `Wind`ness of the source object is lost, sliced away, so that the copy can be an `Instrument`. – user4581301 Mar 17 '23 at 19:06
  • @Mezzoforte Even if slicing weren't an issue, ie if `flute` were passed into `tune()` by reference/pointer instead, the code would still only call `Instrument::play()` instead of `Wind::play()` unless `Instrument::play()` were `virtual` and `Wind` overrode it. Which the original code is not doing. `i.play()` is a static method call at compile-time, and the type of `i` is `Instrument` at compile-time. To have `i.play()` call `Wind::play()` at runtime requires `i.play()` to be dispatched via polymorphism, and that requires `Instrument::play()` to be `virtual` and overridden by `Wind::play()`. – Remy Lebeau Mar 17 '23 at 19:12
  • @RemyLebeau You say that the type of i is Instrument at compile time, and that's just the part that confuses me the most. When the compiler needs to check what argument was passed to it, you say it sees flute as an Instrument object. Is this because of upcasting? (I'm talking about the case where tune accepts a pointer/reference, not the original code that I wrote). – Mezzoforte Mar 17 '23 at 19:15
  • 1
    @Mezzoforte the compiler does that validation at the site where `tune()` is being called, not inside of `tune()` itself. An `Instrument&` reference, or an `Instrument*` pointer, will refer-to/point-at the `Instrument` *portion* of the object that is being passed in. Inside of `tune()`, it just knows about `Instrument` only, nothing more (unless you *explicity* type-cast the reference/pointer to another type, ie via `dynamic_cast` or `static_cast`). – Remy Lebeau Mar 17 '23 at 19:17
  • Potentially helpful reading: [What's the difference between passing by reference vs. passing by value?](https://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value) – user4581301 Mar 17 '23 at 19:20
  • @RemyLebeau Okay, I totally get it now. Thanks! – Mezzoforte Mar 17 '23 at 19:21
  • 1
    @Mezzoforte -- Are you basing your conclusions on what happens with languages like Python or Java (or similar)? If so, then this is why you can't use those languages as models in understanding or writing C++ code. – PaulMcKenzie Mar 17 '23 at 19:21

1 Answers1

0

polymorphic behavior only works through pointers and references.

Do this

void tune(Instrument *i) {
  i->play(middleC);
}


int main()
{
  int a = 1+1;
  Wind flute;
  tune(&flute);
}

plus the methods need to be virtual

class Instrument {
public:
  virtual void play(note) const {
    cout << "Instrument::play" << endl;
  }
};

class Wind : public Instrument {
public:
  
  virtual void play(note) const {
    cout << "Wind::play" << endl;
  }
};
pm100
  • 48,078
  • 23
  • 82
  • 145
  • I tried just the first part, and I'm still getting an output of Instrument::play. Is that what I'm supposed to expect? – Mezzoforte Mar 17 '23 at 18:51
  • @pm100 A reference would make more sense than a pointer in this example: `void tune(Instrument &i) { i.play(middleC); } ... Wind flute; tune(flute);` – Remy Lebeau Mar 17 '23 at 18:52
  • @Mezzoforte `play()` must be `virtual` in `Instrument` to get the behavior you want. `Wind` can then `override` it – Remy Lebeau Mar 17 '23 at 18:52
  • @RemyLebeau I get that, but I'm wondering why is it that when the compiler is typechecking flute and sees that it is a wind object, that i.play(middleC) doesn't get redefined into the wind version as well. I mean, the COMPILER CLEARLY sees that flute is a wind object not an instrument object, right? By the way, I see my mistake in using the value instead of a reference, and am not asking about that issue anymore. – Mezzoforte Mar 17 '23 at 18:55
  • @Mezzoforte public inheritance means that any `Wind` "is a" `Instrument`, just that to make use of it you need to use pointers or references – 463035818_is_not_an_ai Mar 17 '23 at 18:59
  • @Mezzoforte "*why is it that when the compiler is typechecking `flute` and sees that it is a `Wind` object, **that `i.play(middleC)` doesn't get redefined into the wind version***" - the compiler doesn't do that (and CANT do that, well not the way you have coded it). It knows that `tune()` takes an `Instrument`, and that `Wind` derives from `Instrument`, so you can pass `flute` into `tune()`, however inside of `tune()`, it doesn't know that it was passed a `Wind` object, in fact it could be passed *any* type of object that derives from `Instrument`, not just `Wind`. – Remy Lebeau Mar 17 '23 at 19:01