Heterogeneous collection: return type overload

J

jrbcast

Hi all,

Imagine I have something like this:

class Collection
{
std::map<char *, void *> elements;
Collection();
~Collection();

addElement( char * name, void * value )
{
}

/*** I Want to merge next methods into a single one ***/
int getValue( char * name )
{
}

double getValue( )
{
}

}

The purpose of this code would be to have a collection of objects of
different kind. Supposing that inside the class I would be able to
know which is the type of each element... How could I implement a
single function to return the right type?

Thanks in advance
 
V

Victor Bazarov

jrbcast said:
Imagine I have something like this:

class Collection
{
std::map<char *, void *> elements;
Collection();
~Collection();

addElement( char * name, void * value )
{
}

/*** I Want to merge next methods into a single one ***/
int getValue( char * name )
{
}

double getValue( )
{
}

}

The purpose of this code would be to have a collection of objects of
different kind. Supposing that inside the class I would be able to
know which is the type of each element... How could I implement a
single function to return the right type?

What's the "right type"? How do you know which type is right? In most
systems I've seen folks didn't use the return value. They used an
argument or a template argument.

Argument:

bool getValue(char const* name, int& ri)
{
// assign to 'ri' if found, return true
// if not found, don't touch 'ri' return false
}

bool getValue(char const* name, double& rd) // same thing

Template argument:

template<class T> T getValue(char const* name)
{
return SomeHelperClassYouCanSpecialize<T>(this, name);
}

Both solutions require the caller to know which type is right.
Otherwise you need a dynamically typed system, and it ain't C++.

V
 
J

jrbcast

Try "std::map<std::string, boost::any> elements;" instead.

/Leigh

Thanks for your responses. Now it is clear that C++ does not support
function
overloading on return types :-(.

Leigh, I have seen boost::any but I can not add new libraries to my
application,
that is a constrain....

Cheers,
 
M

Michael Doubez

Thanks for your responses. Now it is clear that C++ does not support
function
overloading on return types :-(.

Leigh, I have seen boost::any but I can not add new libraries to my
application,
that is a constrain....

A lot of Boost libraries are header only and will only affect the
compilation stage. You don't need to redistribute Boost library; and
the license is rather business friendly.
 
J

Jeff Flinn

Leigh said:
Rip boost.any out of Boost and use it standalone then.

and IIRC, boost.any is a header only library which makes it's use even
easier. Although I'm not sure if it has dependencies on other boost
libs. There is a bcp tool that lets you pull out subsets of boost and
ensures you get all the dependencies.

Also if you are dealing with a known set of types to be returned
boost.variant might be a better solution.

Jeff
 
J

James Kanze

Using a char* as a key in a map is probably not a very good
idea.
Thanks for your responses. Now it is clear that C++ does not
support function overloading on return types :-(.

Not directly, but it's fairly simple to get the same effect by
means of a proxy:

class Collection
{
public:
class Proxy
{
Collection const* owner;
std::string key;
public:
Proxy(Collection const& owner, std::string const& key)
: owner(&owner)
, key(key)
{
}
template< typename T > operator T() const
{
return owner->... // whatever it takes to get
// the correctly typed value
}
}
Proxy get(std::string const& key) const
{
return Proxy(*this, key);
}
// ...
};
 
J

jrbcast

Using a char* as a key in a map is probably not a very good
idea.

Yes, I realized about that after posting ;-), I am now using
std::string
Not directly, but it's fairly simple to get the same effect by
means of a proxy:

    class Collection
    {
    public:
        class Proxy
        {
            Collection const* owner;
            std::string key;
        public:
            Proxy(Collection const& owner, std::string const& key)
                : owner(&owner)
                , key(key)
            {
            }
            template< typename T > operator T() const
            {
                return owner->...  //  whatever it takes to get
                                   //  the correctly typed value
            }
        }
        Proxy get(std::string const& key) const
        {
            return Proxy(*this, key);
        }
        //  ...
    };

I am afraid I am not able to follow the code (too hard for me and my
usual needs). Can you point me to some book/web... where to learn such
special things?

Cheers
 
J

jrbcast

Which bit(s) don't you understand?

/Leigh

Thank you very much, I did not realize that "proxy" was the key word
to google it :-S. Sorry, too late here after work (Spain = 21:54).

I can not imagine how this proxy hides types management in a
heterogeneous structure like that I am proposing here std::map<
std::string, void *> so the caller does not need to explicitly pass
the type it is expecting, just receive it (the called object will care
about that)...

I have written something like this but I would like (if possible) to
know if there is a more "advanced" or "correct" way to do that:

// PLEASE NOTE CODE HAS NOT BEEN COMPILED YET AND MAY BE ERRONEOUS

#define angle_t double

#define rot(object) *((angle_t*)(object.getValue(std::string("rot"))))
#define tilt(object) *((angle_t*)
(object.getValue(std::string("tilt"))))
#define psi(object) *((angle_t*)(object.getValue(std::string("psi"))))

class objectValues
{
std::map<std::string, void *> values;
objectValues();
~objectValues();

int addValue( std::string name, int value )
{
void * newValue = (void *)(new int(value));
return insertVoidPtr( name, newValue );
}

int addValue( std::string name, double value )
{
void * newValue = (void *)(new double(value));
return insertVoidPtr( name, newValue );
}

int insertVoidPtr( std::string name, void * value )
{
// Return value for "insert" call
pair<map<std::string, void *>::iterator,bool> ret;

ret = values.insert( pair<std::string, void*>(name,value) );

if (ret.second==false)
{
return -1;
}
else
{
return 0;
}
}

void * getValue( std::string name )
{
map <std::string, *void>::const_iterator element;

element = values.find( name );

if ( element == values.end( ) )
{
return NULL;
}
else
{
return element->second;
}
}

bool valueExists( std::string name )
{
map <std::string, *void>::const_iterator element;

element = values.find( name );

if ( element == values.end( ) )
{
return false;
}
else
{
return true;
}
}

void deleteValue( std::string name )
{
map <std::string, *void>::const_iterator element;

element = values.find( name );

if ( element != values.end( ) )
{
values.erase( element );
}
}
}
 
D

DeMarcus

jrbcast said:
Thank you very much, I did not realize that "proxy" was the key word
to google it :-S. Sorry, too late here after work (Spain = 21:54).

I can not imagine how this proxy hides types management in a
heterogeneous structure like that I am proposing here std::map<
std::string, void *> so the caller does not need to explicitly pass
the type it is expecting, just receive it (the called object will care
about that)...

I have written something like this but I would like (if possible) to
know if there is a more "advanced" or "correct" way to do that:

// PLEASE NOTE CODE HAS NOT BEEN COMPILED YET AND MAY BE ERRONEOUS

#define angle_t double

#define rot(object) *((angle_t*)(object.getValue(std::string("rot"))))
#define tilt(object) *((angle_t*)
(object.getValue(std::string("tilt"))))
#define psi(object) *((angle_t*)(object.getValue(std::string("psi"))))

class objectValues
{
std::map<std::string, void *> values;
objectValues();
~objectValues();

int addValue( std::string name, int value )
{
void * newValue = (void *)(new int(value));
return insertVoidPtr( name, newValue );
}

int addValue( std::string name, double value )
{
void * newValue = (void *)(new double(value));
return insertVoidPtr( name, newValue );
}

int insertVoidPtr( std::string name, void * value )
{
// Return value for "insert" call
pair<map<std::string, void *>::iterator,bool> ret;

ret = values.insert( pair<std::string, void*>(name,value) );

if (ret.second==false)
{
return -1;
}
else
{
return 0;
}
}

void * getValue( std::string name )
{
map <std::string, *void>::const_iterator element;

element = values.find( name );

if ( element == values.end( ) )
{
return NULL;
}
else
{
return element->second;
}
}

bool valueExists( std::string name )
{
map <std::string, *void>::const_iterator element;

element = values.find( name );

if ( element == values.end( ) )
{
return false;
}
else
{
return true;
}
}

void deleteValue( std::string name )
{
map <std::string, *void>::const_iterator element;

element = values.find( name );

if ( element != values.end( ) )
{
values.erase( element );
}
}
}

You may be in the same situation as I have at a couple of places, where
I need to store values of any type in a container to be able to process
them later.

Following is a simplification of my own solution, but please note that I
have not found this solution anywhere on the net or in any book, so I
don't know if this is the right way to go. However, it seems to work
pretty good so far. The solution is built on the Visitor pattern.
http://en.wikipedia.org/wiki/Visitor_pattern

In this solution you first have to identify the receiver of your object
values. I.e. it may not be the perfect solution if you want to create a
generic map that can deliver your objects to whatever. In my case I
wanted to deliver a container of any type to std::cout. Basically I
wanted to create a container with contents like this
{ "Hello ", myOwnObject, " number ", 7, std::endl } and I couldn't
calculate it during the call, hence, I first needed to store the values
in a container until they should be used.

Ok, back to step one. I identified the receiver as std::cout. Then I
created an objectValue-to-receiver interface like this.

class ObjectValueInterface
{
public:
virtual ~ObjectValueInterface() {}
virtual void applyTo( std::eek:stream& ) = 0;
// applyTo() would represent accept() in the Visitor pattern.
};

// Then create a template for any object value like this.
// Here you also need to decide whether you want a copy of your object
// or a (smart) pointer to your object. Since you copy in your own
// example I do the copy variant here.

template<typename T>
class ObjectValue : public ObjectValueInterface
{
public:
ObjectValue( T objVal ) : objVal_(objVal) {}
virtual void applyTo( std::eek:stream& os )
{
os << objVal_;
// operator<<() would represent visit() in the Visitor pattern.
}
private:
T objVal_;
};

// Now you create your object value container.
#include <map>

class ObjectValueContainer
{
public:
template<typename T>
int addValue( const std::string& key, T val )
{
if( objVals_.find( key ) != objVals_.end() )
return -1;

objVals_[key] = new ObjectValue<T>( val );

return 0;
}

ObjectValueInterface* getValue( const std::string& key )
{
if( objVals_.find( key ) == objVals_.end() )
return NULL;

return objVals_[key];
}
private:
std::map<std::string, ObjectValueInterface*> objVals_;

// Note! This is a simplified example. You should consider using
// boost::shared_ptr instead of a plain pointer here.
// boost::shared_ptr will be std::shared_ptr with the next
// standard. Until then, see
// www.boost.org/doc/libs/1_42_0/libs/smart_ptr/shared_ptr.htm

};

// Now use it.
// Note! In this example we skip error handling.

int main()
{
ObjectValueContainer objVals;

// Add some values.
objVals.addValue( "Name", "Daniel" );
objVals.addValue( "House Number", 3 );
objVals.addValue( "Double", 47.11 );
objVals.addValue( "Delimiter", " " );

// Print the values.
objVals.getValue( "Name" )->applyTo( std::cout );
objVals.getValue( "Delimiter" )->applyTo( std::cout );
objVals.getValue( "House Number" )->applyTo( std::cout );
objVals.getValue( "Delimiter" )->applyTo( std::cout );
objVals.getValue( "Double" )->applyTo( std::cout );

return 0;
}

Hope this helps!
If anyone else have comments on this kind of solution I would be happy
to hear it so I can find flaws and improve it.

/Daniel
 

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

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top