views:

110

answers:

4

This is a slightly different question to this one ([http://stackoverflow.com/questions/1566445/accessing-a-method-from-a-templated-derived-class-without-using-virtual-functions%5D%5B1%5D) which I asked recently.

I would like to create an instance of an object using a templated factory method, then from then on only be able to set and get a value based on the initial type supplied to the item.

Here's a quick example of what I'm trying to achieve:

boost::shared_ptr<Item> item = Item::create<float>();

item->setValue(5);                  // conversion to 5.0 in setting the value
float value = item->value();        // returned value = 5.0

Essentially the only time setValue will fail is if there isn't an implicit conversion from whatever has been supplied to the initial 'type' of the internal value in Item.

So if I made the whole Item class a templated class taking a type, I would need to provide the type of the value every time I created a shared pointer to the Item, which I don't really care about.

The next approach I took was to try and store the initial type in the class and use boost::any for the internal storage, casting the internal type to the initial type specified if there was an implicit conversion. However, I stuggled to store and compare the type information, initially looking at std::type_info, but as setValue took a boost::any, had no way of comparing what was actually passed.

(A slight extension to this might be providing a variant-style list of options to the template argument in the creation and returning the value in the native supplied type.)

There may be a design pattern I'm not aware of, or a different approach I haven't considered, so I'd be interested in hearing any suggestions of how to tackle this?

+1  A: 

This sounds very similar to boost.any. Have you look at this already?

iain
"The next approach I took was to try and store the initial type in the class and use boost::any for the internal storage..."
Laurence Gonsalves
+2  A: 

Consider this:

boost::shared_ptr<Item> item1 = Item::create<float>();
boost::shared_ptr<Item> item2 = Item::create<string>();

item1 and item2 have the same (static) type: boost::shared_ptr. So there isn't any way you can make this compile:

item2.setValue("foo");

and have this fail to compile:

item1.setValue("foo");

I'm not sure what you're saying about "if I made the whole Item class a templated class taking a type, I would need to define this every time I call setValue or value (setValue<float>(5))". It sounds like you're actually talking about making each method templated, not the whole class. If the Item class was templated then you'd write:

boost::shared_ptr<Item<float> > item = Item<float>::create();

and then you can easily have the setValue() method accept only the matching type.

Laurence Gonsalves
You're right that it can't be a compile-time failure, but I don't read that in the question.
MSalters
The templated class wouldn't require me to define the type for every function call as you correctly pointed out, I've modified the question. It may turn out that this way of doing things is an acceptable compromise, even though it's annoying to need to know the type of the value before created the shared pointer to the Item.
Dan
+2  A: 

Does this work for you?

#include <iostream>
#include <boost/shared_ptr.hpp>

using namespace std;
using namespace boost;

template<typename T> class Item
{
    Item(); // prohibited
    Item (const T & t) : m_value(t) {}
public :
    static shared_ptr<Item<T>> Create(const T & t) 
    { return shared_ptr<Item<T>>(new Item<T>(t)); }
    const T & getValue() const { return m_value; }
    void setValue(const T & v) { m_value = v; }
private :
    T m_value;
};

template<typename T> class Factory
{
public :
    static shared_ptr<Item<T>> CreateItem(const T & value)
    { return Item<T>::Create(value); }
};

void main()
{
    shared_ptr<Item<int>> a = Factory<int>::CreateItem(5);
    shared_ptr<Item<float>> b = Factory<float>::CreateItem(6.2f);
    std::cout << a->getValue() << std::endl;
    std::cout << b->getValue() << std::endl;
    a->setValue(3);
    b->setValue(10.7f);
    std::cout << a->getValue() << std::endl;
    std::cout << b->getValue() << std::endl;
}
  • edit - enforced more const-ness nazism.
Maciek
Almost exactly what I'm after, except that the programmer would require knowing the type of the Item to formulate the shared pointer, additionally, my entire library would require templates to deal with instances of Item being passed in, when I don't necessarily care about the type in most of the functions...
Dan
So in other words : you'd rather have the shared_ptr<Item<int>> be smarter and pick it's own type in some *smart* way ?
Maciek
Assuming the array of types you intend to use is limited :have you considered using boost::variant ? you could just have the Item contain boost::variant<possible types list in here> and initialize it with the correct type...
Maciek
Essentially I need some untemplated base class to use when passing to a function not requiring the generic value implementations, it's just handling the conversion to the correct shared_ptr that I'm struggling on, without doing a crazy if dynamic_cast successful else if dynamic_cast successful etc...
Dan
Yeah I looked at boost::variant, but there's no reason why the array of types should be limited, any type can be accepted as long as there's an acceptable implicit conversion
Dan
+1  A: 

Your problem with boost::any is that it's too flexible. The basic idea behind it is still sane. What you need to do is wrap the initial value in an object of type template<typename T> class ItemOfType<T> : public Item. Item::create<T> would then return such a typed object. When you later try to assign a U, you first test if you can dynamic_cast your this to ItemOfType<U>*. This covers your T==U case.

The harder part is the question whether a U is assignable to a T. The usual solution is to instantiate templates, but in your create<T> you don't have the U and in your Item::operator=(U const) you don't have the original T.

MSalters
This sounds a lot like one of my early attempts at an implementation, which suggests I'm making progress. If I could just store and query on Item what the T was in ItemOfType on creation, when receiving a shared pointer to Item, I'd know what type of shared pointer to create...
Dan
That's the fundamental point: when you get a Item* at runtime, it's too late to instantiate templates. Anything you instantiate, you have to instantiate at compile time. At that point, the points where you have T are unrelated to the points where you have U, so there is no single point where you can instantiate a single template on both T and U. Since the domains of T and U are both infinite, you can't enumerate. Hence, I'm inclining to call it impossible.
MSalters
Think you're right, I'm trying to push this in a direction it's not supposed to go, have made a few compromises to the ideal design and it seems to be working well, albeit type errors get picked up at runtime instead of compile time, but think I've isolated these potential problems sufficiently that it's an acceptable solution.
Dan