Questions: doing in C++ what you can do in TADS3?

Hi to anyone that knows both C++ and TADS3. Again, I’m sure if I dug long enough in my documentation I could probably find an answer (I have two Stroustrup books), but I don’t feel like reading my tomes cover-to-cover right now and so I’ll try this route.

Does C++ have any syntax or mechanism analogous to TADS 3 for:

1. modify [ClassName] ...
2. someMethod(arg) {
   some new statement;
   inherited(arg);             // <---
   }

Also, I only have a superficial acquaintance with C++'s derived classes and constructors. Say I have a library class sf::Sprite, which by a library constructor can take a Texture argument and be created with:

Texture tx;
// ... load an image into the texture
Sprite spr(tx); //Sprite should be created using the texture (SFML)

If I want to use the Sprite class but add some properties (/attributes/data members) and methods, is there a way to modify the Sprite class manually without revising a copy of the library source? Or do I subclass Sprite with MySprite (which is what I suspect?), in which case how do I define the constructors for MySprite? Do I have to copy the code from Sprite’s constructors, so that MySprite can be created with a Texture argument, or if I define the MySprite class as simply as

class MySprite: public sf::Sprite {
   public:
      additionalMember = value;
  }

can I then do

Texture tx;
MySprite spr(tx);

and it will use Sprite’s constructor? Further, if I want to add some additional initialization in MySprite’s constructor, what is the syntax for calling the superclass constructors to do the general work?
Thanks for any help!

John

I assume the idea here is to call the parent class’s implementation of someMethod. In C++, this looks like

ParentClass::someMethod(arg);

You have to name the parent class explicitly, because in C++ a class can have more than one parent. (Multiple inheritance.)

Yes. The constructor is inherited, so if you don’t need to do any extra work, you get it for free.

There are a few different ways to handle constructor arguments in C++. Older code is written like this:

class MySprite: public sf::Sprite {
   public:
      MySprite() {
          sf::Sprite::MySprite();
          // more initialization here
      }
}

But these days people like to write:

class MySprite: public sf::Sprite {
   public:
      MySprite() : sf::Sprite() {
          // more initialization here
      }
}

(Hope I got all the colons right in that – I haven’t run them through a compiler to verify.)

1 Like

Andrew, thanks for the help.
I thought I had already tried

MySprite sprite(texture);

and gotten an error saying that MySprite didn’t have any constructors that took a Texture argument, but I might have had something mistyped somewhere. I’ll try that again.
As for inherited calls: TADS3 is also multiple inheritance; as far as I know inherited calls all of the constructors of all the superclasses (or parent versions of any other method) in the correct order of declaration as far as what overrides what.
So let’s say that we have

class MyClass : ClassA, ClassB {
    // do I need to explicitly call constructors of every superclass here?
  //MySprite() : ClassA(), ClassB() { init } ?

Also, if ClassA or B require arguments, does MySprite() need to contain matching args between its parentheses and then call the parent constructors after the colon with those args?

Then, modify… are there other ways of accomplishing this in C++? It feels pretty indispensable for programming a text adventure to work the way you want, while still getting the library to do all the grunt work. If you don’t know the TADS lingo, here’s an example:

// This is in a library source file:

class Thing: object
    bulk = 1
    // a hundred other attributes and methods 
    ;
class Actor: Thing
    bulk = 10
    isHim =  nil
    isHer = nil    // an Actor could be an animal or even a group:
                        // don't presume any gender
    // many more Thing overrides and unique properties
    ;

///////// now this is in your own game source

class MyOwnClass: Thing
    // stuff
    ;
modify Thing
    bulk = 5       // we have a different system in mind than the library 
                  // default, so we want a different default
    ;
modify Actor
    /* our game is entirely composed of female actors: set the default
    so we don't have to specify this for every instance */
    isHer = true
    // our bulk will still == 10 because Actor overrides Thing, even with
    // the modify statement
    ;

The example shows how it works, although changing a few simple default attributes isn’t all that important. It’s much more important for when you modify methods of key action processes, where places scattered all over the library will be calling that method. You’re literally changing how the library works to suit your needs, without having to alter the original source files, because the changes are only compiled into your game.

One last issue, if you can help! In TADS3 I am used to statically declaring instances of game world objects; typically you don’t need to use new or constructors, and each object has a global identifier (if you want it), and can be referenced from anywhere in game code.

//top-level statement
magicWardrobe: EntryPortal, Container, Thing
    // stuff
    ;

//now 'magicWardrobe' is a symbol than can be used anywhere in
//game code: a reference to the statically created object

Now, without really having a good understanding of how a VM works or how TADS source compares to Python or C++, I assume that these objects are sort of like global variables? Yet I hear that global variables are not good coding practice, so this leads me to ask:
What is the correct C++ way to instantiate lots of small objects? Is it supposed to be done in the main function? Where should they be stored? How do you refer to them (let’s say we’re talking about sprites again) from anywhere else in your code? After learning TADS first, Python really bugged me by not being able to refer to other instance objects in the program by simply using a unique identifier: it was always self.abc or self.parent.otherchild.attributeStoringAnObject.
Thanks again!

Not to my knowledge, no. You can define a class that inherits from Thing (or what have you), but if the library calls new Thing, then it creates a Thing in its original definition, and there is no way to prevent that.

You might be able to work around this, though, if you get creative with typedef/using:

class MyThing: public Thing {
    // code
};

using LibThing = MyThing;

Then as long as the library uses new LibThing, makes its own classes inherit from LibThing, etc., you almost get a “modify” directive. But I don’t know if that would actually work in a larger project.

They’re more like global constants, you can’t assign to them:

magicWardrobe: EntryPortal, Container, Thing
    // stuff
    ;

somethingElse: object
    doStuff() {
        magicWardrobe = somethingElse;  // not allowed
    }
;

In C++, you could just do the same:

MagicWardrobeCls * const magicWardrobe = new MagicWardrobeCls;

and those would all be executed before calling your main function. But, depending on what you’re doing, you might be placing your “small objects” in an attribute of an object, or in a map, or a vector.

1 Like

That’s just not true in IF programming. Or a lot less true, anyhow. IF really wants a lot of singleton objects of custom classes, and IF languages go out of their way to make this easy. C++ does not.

If you try too hard to apply standard engineering practices to IF game code, you wind up going down some unnecessary alleys.

Right… I wasn’t questioning the validity of global singletons in IF… rather, I was saying that’s the only way I’ve known to do things (having learned programming through TADS3), but I’m gathering that this is not the normal practice in the C++ world (for non-IF programs). Therefore, I was wondering what the conventional approach would be in C++: when and where do you create and then store all your instances? Also, how do you refer to these instances from other parts of code?