C++/a wizard data type
Expert: Ralph McArdell - 11/17/2006
QuestionHello...
I'm wondering if there a data type like that:
//
Data_Yype x;
int y=5,a;
cin>>a;
if (a==1) {
put (cout<<) into x
}
else {
put (cin>>) into x
}
xy;
//
then x will do the operation it contains:
prints the value of y
or reads a value to y
I need this kind of data type to do a lot of things in my assignment
//
void search (.....) {
Data_Type x;
if (i==0) {
Clinics* currPtr;
currPtr=Hospital[n].Clinic
put (.name) into x
}
else if (i==1) {
Doctors* currPtr;
currPtr=Hospital[n].Clinic->Doctor
put (.fname) into x
}
else if (i==2) {
Doctors* currPtr;
currPtr=Hospital[n].Clinic->Doctor
put (.lname) into x
}
.
.
.
}
search for (of type string/int it depends) in currPtr(x)
// when i==2
// currPtr(x) means Hospital[n].Clinic->Doctor.lname
Not Found, search on currPtr->next
I know this is almost impossible.. I don't want to make a function for each case, but it seems it's the only way
Thanks...
AnswerIn a word no. The types involved in the first case are different for a start, and the latter case is just bad C++ syntax.
However that does not mean that you cannot get similar effects. For example you could create a type constructed from two IO Stream object references (or pointers): one to a std::istream and the other to a std::ostream:
in_or_out_type io( std::cin, std::cout );
cin>>a;
io(a, y);
The class in_or_out_type might look like so:
class in_or_out_type
{
public:
in_or_out_type( std::istream & in, std::ostream & out )
: in_(in), out_(out)
{
}
void operator()( int direction, int & value )
{
switch ( direction )
{
case 0:
in_ >> value;
break;
case 1:
out_ << value;
break;
default:
throw std::invalid_argument
("direction should be 0 (in) or 1 (out)" );
}
}
private:
std::istream & in_;
std::ostream & out_;
};
Of course this only works for values that are of int type (as in your example). To make the call operator (operator()) more generic we can make it a member function template like so:
class in_or_out_type
{
public:
// ...
template <typename T>
void operator()( int direction, T & value )
{
// ...
}
}
// ...
};
Doing so will cause the compiler to generate one version of the call operator function for each value type you pass to it, so it does not obviously save code but does save typing. It might save code over the case of not using a member function template where you added lots of call operator overloads to cater for all predicated uses, some of which were not used.
Note that you will require a reasonably modern compiler to make use of member function templates.
Also note that the example code shown here is just that - an example. Like all code in this reply it is not production quality code, and I have not compiled it (so sorry for any typos or other errors!). They are also certainly not the only solutions, just the ones that occurred to me at the time of writing off the top of my head, so you might like to sit back and see if you can see any alternatives to the solutions presented here.
Another possibility would be to use polymorphism. The base would define the operation to be performed; the two derived variations would cater for the input and output cases. You would probably then have to wrap the pieces up in a class or function to pull it all together:
class in_or_out_base { ... };
class input : public in_or_out_base { ... };
class output : public in_or_out_base { ... };
// ...
in_or_out_base * create_new_in_or_out( int direction )
{
switch ( direction )
{
case 0:
return new input( cin );
case 1:
return new output( cout );
default:
throw std::invalid_argument
( "direction should be 0 (in) or 1 (out)" );
}
}
The main sticking point I can see here is that a member function template cannot be virtual. This is because they would define a possibly infinite, but unknown at the time a class is defined, number of potential virtual functions so the compiler cannot know the size or contents of the structures necessary to manage virtual functions and polymorphic behaviour (typically this is in the form of a table, called the vtable that contains one pointer to each virtual function).
The way around this would be to make the three classes class templates and the function a function template each taking 1 parameter, the type of the value to be input or output.
My final point of warning with the current outline of this scheme is that it requires the dynamic creation of the input/output types so be sure they are deleted when no longer required (in fact using a smart pointer to hold them is probably a good idea).
Now to your second example. This case as far as I can tell from the example is that you wish to be able to search a single set of data by checking for matches to various different pieces of data held in the records.
You do not need to re-write the whole of the search logic for each search case. You only need to supply the correct comparison logic for each case. This is how you work with the algorithms in the C++ standard library, for example:
#include <list>
#include <algorithm>
// ...
class MatchClinicName
{
MatchClinicName( std::string const & name )
: name_( name )
{
}
bool operator( Hospital const & hospital ) const
{
return hospital.Clinic.name == name_;
}
private:
std::string name_;
};
class MatchDoctor1stName
{
MatchDoctor1stName( std::string const & name )
: name_( name )
{
}
bool operator( Hospital const & hospital ) const
{
return hospital.Clinic->Doctor.fname == name_;
}
private:
std::string name_;
};
// etc...
// ...
std:list<Hospital> hospitals;
// ...
std:list<Hospital>::iterator matched_position;
matched_position = std::find( hospitals.begin()
, hospitals.end()
, MatchClinicName("Antinatal")
);
// ...
matched_position = std::find( hospitals.begin()
, hospitals.end()
, MatchDoctor1stName("Edmond")
);
Again, you will see that we are using classes that define a call operator, operator(). The name for such objects is Function Objects or Functors. In this case they represent what the C++ standard calls Unary Predicates. Unary because they take a single argument, and Predicate because they answer a true/false question, i.e. they return a Boolean value. The reason we need to do this is because we wish to perform specialised searches on values of specific members of the types in your collection and to do this we need two things:
- the value to compare against
- the logic to perform the comparison
The first is provided by the constructor and state of the functor classes and the latter by their operator() implementation.
I should note that the unary predicate operation need not be provided by a functor class instance. It could be implemented as an ordinary function if such will suffice. In this case however we need to associate the operation with some state - the value to match, so a functor class is the way to go.
The functors can be used with the standard library algorithms such as std::find. The algorithms work over one or more ranges, usually associated with a sequence of items in some collection. In this example the collection is a std::list of Hospital types (I have tried to use names for types from your example, but you did not show the type names only the object names - Hospital seems to be an array or other collection of some type representing hospital data in your example; I have used it as a type name in my example). The list begin and end member functions return iterators (generalisations of pointers, i.e. position) pointing to the first and one-past-the-last items in the list. The value returned from std::find is an iterator representing the position of the fist item in the sequence to match the criteria or the end value of the sequence searched if no match was found.
So each use of std::find tries to locate matching Hospital items in the sequence from the first to last Hospital objects in the hospitals list. It determines a match by passing each Hospital object (by const reference) to the operator() function of a copy of the temporary functor passed to find. The temporary functor instances are constructed using the value to be found.
In theory it would be possible to create a functor unary predicate that could compare the string value to one of a number of members of the Hospital type and its related members. This could be expanded to include int values as well:
class HospitalMatch
{
HospitalMatch( int mode, std::string const & data )
: strData_( data ), mode_( mode )
{
// check mode_ is valid, throw exception if not
// e.g. std::invalid_argument
}
HospitalMatch( int mode, int data )
: intData_( data ), mode_( mode )
{
// check mode_ is valid, throw exception if not
// e.g. std::invalid_argument
}
bool operator( Hospital const & hospital ) const
{
switch ( mode_ )
{
case 0:
return hospital.Clinic.name == strData_;
case 1:
return hospital.Clinic->Doctor.fname == strData_;
case 2:
return hospital.Clinic->Doctor.lname == strData_;
// ...
default:
throw std::domain_error("Invalid HostitalMatch mode");
};
}
private:
std::string strData_;
int intData_;
int mode_;
};
You will notice that the above all-in-one matching functor class is a bit more complex that the classes used to match for individual cases and that it now has to protect against the match mode value being invalid, which means you will have to add code to handle these failures (i.e. try ... catch blocks).
If you wish to know more about C++ features such as templates then a good starting point is the C++ FAQ at:
http://www.parashift.com/c++-faq-lite/ Section 35 is about templates. I suggest you read all of this before asking elsewhere - it is probable that many of your basic questions will be answered by the FAQ.
If you wish to know more about the facilities of the collections, algorithms etc. of the C++ standard library then I suggest you obtain a good reference such as "The C++ Standard Library A Tutorial and Reference" by Nicolai M. Josuttis. There is some on line reference material available at
http://www.sgi.com/tech/stl/
If you want more details abut templates then Nicolai M. Josuttis in conjunction with David Vandevoorde have written a good book on the subject: "C++ Templates The Complete Guide".