C++ Template Virtual Function

V

Vishal

Hi,

I have Class A and according to my requirements the implementation of
the class is fixed. Class A manages the setting/getting of values.
The values stored can be of type integer, unsigned integer, character,
unsigned character and std::strings. The available types can grow in
the future. I'm having trouble finding a manageable way of storing
values of different types with the ability of retrieving them. The
problem area is class B's getValue method. It can't be a virtual
template method of a non template class. So, how should I provide a
unified way for class A to get a list of all the stored values that
match the tag value being passed?

I don't need to have a class B and C. If there is a better way that
only has one class, it's okay. I do want to avoid just having a bunch
of switch statements that executes different code based on different
types. This is what I think is unmanageable as new type are required.


#include<list>


class B
{
private:
unsigned long m_Tag;

public:

B(unsigned long tag) : m_Tag(tag) {}

template<typename T>
virtual int getValue(std::list<T> &val) = 0;


}; // end of class B


template<typename T>
class C : public B
{
private:
T m_Value;

public:
C(unsigned long const& tag, T const& value) : B(tag) {
m_Value = value;
}

virtual int getValue(std::list<T> &val) {
val.push_back(m_Value);
}

}; // end of class C

class A
{
private:
std::list<B> m_Collection;

public:
template<typename T>
setTagValue(unsigned long tag, T const& value) {
m_Collection.push_back(C<T>(tag, value));
}

template<typename T>
getTagValue(unsigned long const tag, std::list<T> values) {
for(auto i = m_Collection.begin(); i != m_Collection.end(); i++) {
if(i->getTag() == tag) {
i->getValue(values);
}
}
}


}; // end of class A







int main (void)
{
A collection;

collection.setTagValue(2, "hello");
collection.setTagValue(3, static_cast<int>(3));

std::list<int> values;

collection.getTagValue(3, values);
};
 
V

Vishal

There is no such thing as a virtual template function. This would require
recompiling all classes in the class hierarchy and adding vtable slots to
all classes whenever a new specialization happens to be called on *any*
class in the hierarchy. This would mean some serious whole program
analysis and would not probably work with DLL-s anyway.

That said, it seems you are wanting some kind of type erasure feature. In
principle this means to store pointers to dynamically allocated instances
of different types in your container, and upon retrieval restore the type
somehow. This sounds similar to boost::any, maybe you can use this.


You can't have a list of base class B objects here; when storing derived
C objects as B they would be sliced. That's why you need to store
pointers instead. The boost::any class encapsulates this pointer
mechanism AFAIK and hides such details from you.

In any case, bear in mind that the template mechanisms are compile-time
only and cannot dispatch the execution based on information which becomes
available at run-time only. This probably means that if the run-time type
of the objects is determined by a "tag", then the tag must appear as a
template parameter in your interface, in order to avoid run-time switch
statements.

hth
Paavo

There is no such thing as a virtual template function. This would require
recompiling all classes in the class hierarchy and adding vtable slots to
all classes whenever a new specialization happens to be called on *any*
class in the hierarchy. This would mean some serious whole program
analysis and would not probably work with DLL-s anyway.

You are correct. I discovered the same thing when this didn't work.
It was unfortunate.

That said, it seems you are wanting some kind of type erasure feature. In
principle this means to store pointers to dynamically allocated instances
of different types in your container, and upon retrieval restore the type
somehow. This sounds similar to boost::any, maybe you can use this.

I'm familiar with boost::any. I had hoped to use it but couldn't
figure out how to avoid the type cast exception failures if the wrong
type was asked for. Since, I know that only one type will be correct
I don't want the cost of throwing exceptions to find the correct
type. I've ended up storing the type that was passed in so I could
figure out which cast to use. This avoids my issue of using a wrong
cast, but now I need a switch statement.

//Example casting
switch(a)
{
case: eType::float: cast here
}

In any case, bear in mind that the template mechanisms are compile-time
only and cannot dispatch the execution based on information which becomes
available at run-time only. This probably means that if the run-time type
of the objects is determined by a "tag", then the tag must appear as a
template parameter in your interface, in order to avoid run-time switch
statements.

I'm vaguely familiar with tag dispatching. How would you use tag
dispatching with getValues<T>? Would this design fix the switch
statement issue I discussed earlier? I would like to have a single
place to add new types and not change A. Is that possible with tag
dispatching?

Thanks

Vishal
 
V

Vishal

I'm vaguely familiar with tag dispatching. How would you use tag
dispatching with getValues<T>? Would this design fix the switch


I've have included an updated version of the example code that uses
boost::any.

B::getValue shows the "switch" like statement that I want to avoid
needing to modify as new types are added.

How do you call getValues<T>? More specifically, how do you specify T when
calling it? How is T related to the type of the stored items?

Class B::getValue shows how I'm relating my stored type with T. Can
tag dispatch end my suffering?


struct eType
{
enum Type {
short,
long
char
};
};

class B
{
private:
unsigned long m_Tag;
boost::any m_Value;
eType::Type m_Type;

protected:
template<typename T>
eType::Type getType(void) {
if (boost::is_same<T, short>::value ) return eType::short;
if (boost::is_same<T, long >::value ) return eType::long;
if (boost::is_same<T, char >::value ) return eType::char;

return eType::char; //Should never get to this return.
}

public:
B(unsigned long tag, boost::any & value, eType::Type type)
: m_Tag(tag)
, m_Value(value)
, m_Type(type)
{
BOOST_STATIC_ASSERT(boost::is_same<T, short>::value ||
boost::is_same<T, long>::value || boost::is_same<T, char>::value);
}

template<typename T>
int getValue(std::list<T> &val) {
if ( m_Type == eType::short )
val.push_back(boost::any_cast<short>(m_Value));
if ( m_Type == eType::long ) val.push_back(boost::any_cast<long>
(m_Value));
if ( m_Type == eType::char ) val.push_back(boost::any_cast<char>
(m_Value));

return 0;
}
}; // end of class B


template<typename T>
class C : public B
{
public:
C(unsigned long const& tag, T const& value) : B(tag, value,
B::getType<T>()) {}
}; // end of class C


class A
{
private:
std::list<B> m_Collection;

public:
template<typename T>
setTagValue(unsigned long tag, T const& value) {
m_Collection.push_back(C<T>(tag, value));
}


template<typename T>
getTagValue(unsigned long const tag, std::list<T> values) {
for(auto i = m_Collection.begin(); i != m_Collection.end(); i++) {
if(i->getTag() == tag) {
i->getValue(values);
}
}
}
}; // end of class A

int main (void)
{
A collection;

collection.setTagValue(3, static_cast<int>(3));

std::list<int> values;

collection.getTagValue(3, values);
};
 
V

Vishal

The getType() method does not make much sense. It just translates from T
to the enum values. First, there is no reason for it to be a member
function of B. Second, there is an run-time dispatch which is not needed
as everything is known at compile-time. Typically, this kind of
translation is done by some kind of traits class (which typically
contains other useful stuff as well), e.g.:

Thanks for the traits example. That makes sense.

In getValue(), it seems inner type m_Type is in no way related to the output type T.

Not correct. The inner type is comparing it's value against the
passed type.
If the types do not match, your code converts
the data, possibly truncating the values.

If the types don't match the code doesn't add anthing to the passed
list. So, how is the code converting data?

If you were able to guarantee that the client code always calls getValue
with the correct (matching) T, then it would be indeed trivial to write a
shorter version:

template<typename T>
int getValue(std::list<T> &val) {
   val.push_back(boost::any_cast<T>(m_Value));
   return 0;

}

Since it's not guaranteed, couldn't we check the types with the tag
dispatch like so?

template<typename T>
int getValue(std::list<T> &val) {
if ( m_Type == getType<T>() )
val.push_back(boost::any_cast<T>(m_Value));
return 0;
}
 
J

Jeff Flinn

You are correct. I discovered the same thing when this didn't work.
It was unfortunate.



I'm familiar with boost::any. I had hoped to use it but couldn't
figure out how to avoid the type cast exception failures if the wrong
type was asked for. Since, I know that only one type will be correct
I don't want the cost of throwing exceptions to find the correct
type. I've ended up storing the type that was passed in so I could
figure out which cast to use. This avoids my issue of using a wrong
cast, but now I need a switch statement.

//Example casting
switch(a)
{
case: eType::float: cast here
}

Have you watched the video of Sean Parent's boostcon/C++ Now talk at:


The recently accepted boost type erasure library by Steven Watanabe may
be useful to you.

http://steven_watanabe.users.sourceforge.net/type_erasure/libs/type_erasure/doc/html/index.html

Jeff Flinn
 
V

Vishal

For reference, I repeat the getValue() code here:
template<typename T>
int getValue(std::list<T> &val) {
if ( m_Type == eType::short ) val.push_back(boost::any_cast<short>(m_Value));
if ( m_Type == eType::long ) val.push_back(boost::any_cast<long> (m_Value));
if ( m_Type == eType::char ) val.push_back(boost::any_cast<char> (m_Value));

return 0;
}

Again, you are corrrect. I meant to place a double if statement.

Yes, if this is OK regarding your app logic, you can do this. For this
you won't need the whole eType system though as boost::any knows its
stored type anyway:

I must be missing something. Boost any may know what type it but the
documentation doesn't specify a way to get that information. The
examples show the follow way to determine type. I really don't want
to throw exceptions for incorrect type cast.

try
{
any_cast<const char *>(operand);
return true;
}
catch(const boost::bad_any_cast &)
{
return false;
}
 

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

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top