Templates and Trading Cards

K

kelvSYC

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);
 
D

Dave Moore

kelvSYC said:
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
 
S

Siemel Naran

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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

Templates and g++ 4
Cards deck problem 13
Templates 5
Only one table shows up with the information 2
[SUMMARY] Counting Cards (#152) 3
Sizewell B++ 0
Rods 0
Lowering Of Graphite Rods Into Reactor Core 1

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top