Templates and Trading Cards

Discussion in 'C++' started by kelvSYC, Apr 19, 2004.

  1. kelvSYC

    kelvSYC Guest

    I'm trying to program something along the lines of a "trading card"
    idea: I have a single copy of the "card" in the program, yet there may
    be multiple "instances" of the "card", with differing information (such
    as the owner of the "card") in each instance.

    struct Person;

    Person Bob;
    Person Joe;

    struct Card; /* each instance of this represents different trading
    cards*/
    struct BaseballCard : public Card;
    struct HockeyCard : public Card;

    BaseballCard BabeRuthCard;
    HockeyCard WayneGretzkyCard;

    /* each instance of this represents a different copy of a type of
    trading card */
    template<class T> struct CardInstance<T> {
    Person owner;
    T& card;
    };

    CardInstance<BaseballCard> BobsBabeRuthCard;
    CardInstance<HockeyCard> JoesWayneGretzkyCard;

    How would I use templates to construct a function createCard(Person) in
    Card (or its subclasses) that returns the appropriate subclass of Card
    so that I can do something like this:

    CardInstance<BaseballCard>& BaseballCard::createCard(Person& p);
    CardInstance<HockeyCard>& HockeyCard::createCard(Person& p);

    /*
    The body for CardInstance<T>& T::createCard(Person& p) would be
    something like

    return {owner, *this};
    */

    CardInstance<BaseballCard> BobsBabeRuthCard =
    BabeRuthCard.createCard(Bob);
    CardInstance<HockeyCard> JoesWayneGretzkyCard =
    WayneGretzkyCard.createCard(Joe);

    ------------

    Or, is this not the way to go, and that I should rethink the design?
    If so, what could a possible design look like?

    --
    I am only a mirage.
     
    kelvSYC, Apr 19, 2004
    #1
    1. Advertising

  2. kelvSYC

    Dave Moore Guest

    kelvSYC <> wrote in message news:<190420041620356294%>...
    > I'm trying to program something along the lines of a "trading card"
    > idea: I have a single copy of the "card" in the program, yet there may
    > be multiple "instances" of the "card", with differing information (such
    > as the owner of the "card") in each instance.
    >

    Hmm .. this sounds like a homework problem, so no complete answers ..
    but I'll try to diagnose your issues and point you in the right
    direction.

    First off, you seem to be mixing the object-oriented and generic
    programming paradigms ... IOW inheritance and templates. (This is
    easy to do at first in C++ if you don't have a strong background in
    programming language theory .. I know this first hand 8*)

    Lets look at your code first ...

    > struct Person;
    > Person Bob;
    > Person Joe;
    >
    > struct Card; /* each instance of this represents different trading
    > cards*/


    The comment above is an incorrect use of the term "instance" ... an
    instance traditionally refers to a function or class that is
    automatically generated from a template specification. What you
    should say is that Card provides a base_type from which more specific
    card-types can be derived.

    > struct BaseballCard : public Card;
    > struct HockeyCard : public Card;


    Here you declare two types derived from public Card (you should really
    put in the empty brackets {} before the semi-colon to fully specify
    the class)

    > BaseballCard BabeRuthCard;
    > HockeyCard WayneGretzkyCard;


    Here you declare "BabeRuthCard" as an object of type BaseBallCard, and
    "WayneGretzkyCard" as an object of type HockeyCard.

    > /* each instance of this represents a different copy of a type of
    > trading card */
    > template<class T> struct CardInstance<T> {
    > Person owner;
    > T& card;
    > };
    >


    The comment again tells me you have the wrong idea, because the term
    "copy" is generally applied to objects ... *not* user-defined types
    (classes and structs), which is what the instances of CardInstance<T>
    represent. A template class allows the user to define a method for
    the compiler to automatically generate new *types* (not objects),
    according to a specified pattern -- the template.

    > CardInstance<BaseballCard> BobsBabeRuthCard;


    Here you tell the compiler to automatically generate a new type
    CardInstance<BaseballCard>, and you declare an object of that type
    (BobsBabeRuthCard). Note that there is absolutely NO relationship
    between the BobsBabeRuthCard object and the BabeRuthCard object
    declared above ... your description of the problem indicates that is
    not what you intended

    > CardInstance<HockeyCard> JoesWayneGretzkyCard;


    (See comment above)

    > How would I use templates to construct a function createCard(Person) in
    > Card (or its subclasses) that returns the appropriate subclass of Card
    > so that I can do something like this:
    >
    > CardInstance<BaseballCard>& BaseballCard::createCard(Person& p);
    > CardInstance<HockeyCard>& HockeyCard::createCard(Person& p);
    >
    > /*
    > The body for CardInstance<T>& T::createCard(Person& p) would be
    > something like
    >
    > return {owner, *this};
    > */
    >


    This is non-sensical ... you are declaring a function createCard to be
    in the scope of class T, which is not allowed, because the class T
    must already be completely defined, and you cannot add to it.

    > CardInstance<BaseballCard> BobsBabeRuthCard =
    > BabeRuthCard.createCard(Bob);
    > CardInstance<HockeyCard> JoesWayneGretzkyCard =
    > WayneGretzkyCard.createCard(Joe);
    >


    Ok .. the above declarations make no sense in terms of C++ (see last
    comment), but they do make it clear what your intent is.

    > Or, is this not the way to go, and that I should rethink the design?
    > If so, what could a possible design look like?


    Well .. I guess you have figured out already that this is not the way
    to go .. I will give you some suggestions:


    1) Get a good reference on C++ and review the difference between
    object-oriented programming and generic programming. The C++
    Programming Language (3rd. edition), by Bjarne Stroustrup gives a good
    description, but that is not necessarily the best book for beginners
    (if you are one). For Book reviews see www.accu.org.

    2) Think carefully about *exactly* what you want to do ... lay it out
    on paper. This is by far the hardest and most important part of
    programming ... and failure to address it correctly is the biggest
    reason why it gets screwed up. I will try to get you started:

    Your Person and Card structs are a good start, but you should lay them
    out more completely, and switch to classes to provide more inherent
    "safety" (if you don't understand this comment .. back to the books to
    look up the difference between struct and class)

    #include <string>

    using std::string;

    class Person {
    string name;
    public:
    Person (const string &n) : name(n) {}
    };

    class Card {
    // provide general characteristics common to all sports cards
    // For example:
    string name_of_featured_star;
    string position_played;
    public:
    Card (const string &n, const string p&) : name_of_featured_star(n),
    position_played(p) {}
    };

    class BaseballCard : public Card {
    // provide stat categories specific to baseball
    double RBI, hits, errors;
    public:
    // define appropriate constructor
    };

    Hopefully you get the idea ... now, you also want to represent the
    idea of multiple copies of a specific instance of a card, each copy
    belonging to a different owner ... to do this using templates is
    actually quite tricky, most likely involving "traits" types and other
    fairly advanced concepts.

    OTOH, since presumably only the owner differs between different copies
    of the card (and perhaps quality, in a more advanced example), you
    could create an owner list in the Card base_type, perhaps using the
    vector class from the STL (Standard Template Library), and also some
    basic functions to make use of it.

    #include <vector>

    class Card {
    // other stuff
    private:
    std::vector<Person> owner_list;
    public:
    bool in_list(const Person& p) const {
    // determine if p is already in list (I'll let you figure out how)
    }
    void add_owner(const Person& p) {
    if (!in_list(p)) // use the function defined above
    owner_list.push_back(p); // add person to end of list
    }
    const vector<Person>& get_owners() const {return &owner_list;}

    // etcetera
    };

    Since you have added the owner list to the base class, it is
    automatically provided to derived classes as well.

    Admittedly, this is not the best possible representation of your
    problem ... just a simple one .. here are some other possibilities:

    You could use a std::vectors to create lists of Person objects and
    (derived) Cards, and then use a hash table to define the ownership
    relationships between the cards and people ... sort of a database
    idea.

    You could also "lift-up" the Person type, adding a std::vector<Card *>
    to represent the list of cards owned by a particular person ... this
    idea actually conforms better to your initial specification of the
    problem. This would then require some work with virtual functions
    (run-time polymorphism) to access the data stored in the derived
    types. IOW, you would have one list containing all cards (baseball,
    hockey, figure-skating, NASCAR, whatever) owned by the persion, and
    then use virtual functions to ensure that the data is extracted
    properly from the cards (i.e. ERA from baseball, or goals scored for
    hockey). I would probably try this design at first myself as a
    "natural" representation of the problem. It also extends naturally to
    allow storage of copy-specific information (like quality):

    enum card_quality { CRAP, GOOD, FINE, MINT};

    class card_copy {
    Card* the_card;
    card_quality the_quality;
    public:
    // you get the idea (I hope)
    };

    Now use a std::vector<card_copy> in Person to represent the list of
    cards owned.

    As you may have guessed, right now I am having more fun with your
    problem then my problem .. hence the long (but hopefully informative)
    answer. I am done spouting off now .. 8*)

    Buena suerte, bon change, veel succes, good luck!

    Dave Moore
     
    Dave Moore, Apr 20, 2004
    #2
    1. Advertising

  3. kelvSYC

    Siemel Naran Guest

    "kelvSYC" <> wrote in message
    news:190420041620356294%

    > struct Person;
    >
    > struct Card; /* each instance of this represents different trading
    > cards*/
    > struct BaseballCard : public Card;
    > struct HockeyCard : public Card;
    > BaseballCard BabeRuthCard;


    > template<class T> struct CardInstance<T> {
    > Person owner;
    > T& card;
    > };
    >
    > CardInstance<BaseballCard> BobsBabeRuthCard;


    Incidentally, the above line should fail to compile because const data in
    the struct, namely card of type T& which is usually internally just a T
    *const, is not initialized. But I guess you're writing illustrative code
    only.


    > How would I use templates to construct a function createCard(Person) in
    > Card (or its subclasses) that returns the appropriate subclass of Card
    > so that I can do something like this:
    >
    > CardInstance<BaseballCard>& BaseballCard::createCard(Person& p);
    > CardInstance<HockeyCard>& HockeyCard::createCard(Person& p);
    >
    > /*
    > The body for CardInstance<T>& T::createCard(Person& p) would be
    > something like


    You should return an object, not a reference to a temporary.

    > return {owner, *this};
    > */
    >
    > CardInstance<BaseballCard> BobsBabeRuthCard =
    > BabeRuthCard.createCard(Bob);


    Solution 1 is to define createCard in each class. You can use recursive
    derivation to save on the typing:

    template <class DerivedCard>
    class CreateCard {
    public:
    CardInstance<T> createCard(const Person& owner) {
    DerivedCard& me = static_cast<DerivedCard&>(*this);
    return CardInstance<T>(owner, me);
    }
    };

    class BaseballCard : public Card, public CreateCard<BaseballCard> {
    ...
    };


    Solution 2 is to use non-member functions

    template <class T>
    CardInstance<T> createCard(const Person& owner, T& card) {
    return CardInstance<T>(owner, card);
    }


    Solution 3 solves a slightly different problem, but may be what you're
    leaning towards. Create a base CardInstance class.

    template<class T> struct BaseCardInstance {
    public:
    virtual ~BaseCardInstance();
    private:
    Person owner;
    };

    Only thing to wonder about is we have what is essentially an abstract class
    with private data.

    In class Card define a pure virtual function createCard that returns a
    BaseCardInstance.

    class Card {
    public:
    virtual ~Card();
    std::auto_ptr<BaseCardInstance> createCard(const Person& owner) = 0;
    };

    In the derived Card classes override createCard. Note that the recursive
    derivation trick of solution 1 does not work here as the inherited
    createCard function is a function from a different hierarchy, namely
    CreateCard, and so does not intefere with or override Card::createCard. You
    can still use the template trick of solution 2 to save on the typing. Try
    to stay away from macros to save on the typing.
     
    Siemel Naran, Apr 20, 2004
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jake
    Replies:
    1
    Views:
    2,185
    Eric J. Roode
    Dec 13, 2005
  2. Bruno Georges
    Replies:
    0
    Views:
    342
    Bruno Georges
    Aug 6, 2004
  3. JKop
    Replies:
    3
    Views:
    492
  4. robietrader
    Replies:
    0
    Views:
    802
    robietrader
    Jan 7, 2007
  5. recover
    Replies:
    2
    Views:
    828
    recover
    Jul 25, 2006
Loading...

Share This Page