C++ class with template member variable
Solution 1
You got very close. I added a few bits because they're handy
class ParameterBase
{
public:
virtual ~ParameterBase() {}
template<class T> const T& get() const; //to be implimented after Parameter
template<class T, class U> void setValue(const U& rhs); //to be implimented after Parameter
};
template <typename T>
class Parameter : public ParameterBase
{
public:
Parameter(const T& rhs) :value(rhs) {}
const T& get() const {return value;}
void setValue(const T& rhs) {value=rhs;}
private:
T value;
};
//Here's the trick: dynamic_cast rather than virtual
template<class T> const T& ParameterBase::get() const
{ return dynamic_cast<const Parameter<T>&>(*this).get(); }
template<class T, class U> void ParameterBase::setValue(const U& rhs)
{ return dynamic_cast<Parameter<T>&>(*this).setValue(rhs); }
class Diagram
{
public:
std::vector<ParameterBase*> v;
int type;
};
Diagram can then do stuff like these:
Parameter<std::string> p1("Hello");
v.push_back(&p1);
std::cout << v[0]->get<std::string>(); //read the string
v[0]->set<std::string>("BANANA"); //set the string to something else
v[0]->get<int>(); //throws a std::bad_cast exception
It looks like your intent is to store resource-owning pointers in the vector. If so, be careful to make Diagram
have the correct destructor, and make it non-copy-constructable, and non-copy-assignable.
Solution 2
The bellow implementation uses a few C++11 features but you will be able to pick them apart.
#include <vector>
#include <memory>
class Parameter
{
private:
class ParameterBase {
public:
virtual ~ParameterBase() {}
virtual ParameterBase* copy() = 0;
virtual void foo() = 0;
};
template <typename T>
class ParameterModel : public ParameterBase {
public:
// take by value so we simply move twice, if movable
ParameterModel(const T& t) : t(t) {}
ParameterModel(T&& t) : t(t) {}
void foo() { t.foo(); }
ParameterModel* copy() { return new ParameterModel(*this); }
private:
T t;
};
public:
template <typename T>
Parameter(T&& t)
: pp(new ParameterModel< typename std::remove_reference<T>::type >(std::forward<T>(t))) {}
// Movable and Copyable only
Parameter(Parameter&&) = default;
Parameter& operator=(Parameter&&) = default;
Parameter(const Parameter& other) : pp(other.pp->copy()) {};
Parameter operator=(const Parameter& other) {
pp.reset(other.pp->copy());
return *this;
};
// members
void foo() { pp->foo(); }
private:
std::unique_ptr<ParameterBase> pp;
};
class Diagram
{
public:
std::vector<Parameter> v;
int type;
};
struct X {
void foo() {}
};
struct Y {
void foo() {}
};
int main()
{
Diagram d;
d.v.emplace_back(X()); // int
// parameters are copyable and can be reassigned even with different
// impls
Parameter p = d.v.back();
Parameter other((Y()));
other = p;
return 0;
}
What does this code do? It hides the fact that we use inheritance to
implement parameters from our users. All they should need to know is
that we require a member function called foo
. These requirements are
expressed in our ParameterBase
. You need to identify these
requirements and add the to ParameterBase
. This is basically a more
restrictive boost::any
.
It is also quite close to what is described in Sean Parent's value semantics talk.
Comments
-
endbegin almost 2 years
I am trying to solve a programming problem that consists of an object (call it Diagram), that contains several parameters. Each parameter (the Parameter class) can be one of several types: int, double, complex, string - to name a few.
So my first instinct was to define my Diagram class as having a vector of template parameters, which would look like this.
class Diagram { private: std::vector<Parameter<T> > v; };
This doesn't compile, and I understand why. So, based on the recommendations on this page How to declare data members that are objects of any type in a class, I modified my code to look like:
class ParameterBase { public: virtual void setValue() = 0; virtual ~ParameterBase() { } }; template <typename T> class Parameter : public ParameterBase { public: void setValue() // I want this to be // void setValue(const T & val) { // I want this to be // value = val; } private: T value; }; class Diagram { public: std::vector<ParameterBase *> v; int type; };
I'm having trouble figuring out how to call the setValue function with an appropriate templated parameter. It is not possible to have a templated parameter in the ParameterBase abstract base class. Any help is greatly appreciated.
P.S. I don't have the flexibility to use boost::any.
-
Mooing Duck over 11 yearsI feel like the
Parameter
class is not needed, and you provide no (clear) way to do assignment, reassignment, or get at the hidden data. :( But since you have it, why did you make it non-copiable? -
aschepler over 11 yearsThis makes it awfully easy to accidentally store a reference to temporary in a
Diagram
. -
pmr over 11 years@aschepler Thanks. I thought about that while coding. Added
remove_reference
. -
endbegin over 11 yearsThanks for your response. I am a little behind the (C++) times, and I am constrained by VS2008. Will spend some time trying to absorb this.
-
pmr over 11 years@MooingDuck Adding a virtual
copy
toParameterBase
would be necessary. The code is unfortunately already more complex than I would like it to be. Feel free to add it. Maybe it would be better for OP to also remove the rvalue references. -
pmr over 11 years@endbegin I edited the code so ParameterS are also copyable and reassignable. This essentially is the same as a
setValue
function. -
Mooing Duck over 11 years@pmr: Yeah, sorry, I forgot it would require a virtual copy. In that case the deleted copy makes sense. Sorry for suggesting that you make the answer more confusing :(
-
endbegin over 11 yearsHad to make some minor changes to your test code, but this works well. Thanks.
-
Mooing Duck over 11 years@endbegin: What changes did you have to make? If I have errors in my answer, I'll fix them up for people who have similar issues in the future.
-
Mooing Duck over 11 years@endbegin: as a note, this is almost exactly how
boost::any
works. -
endbegin over 11 yearsNothing major: Initialized a diagram type
Diagram d;
and a parameterParameter<std::string> p1("Hello");
To add to the vector, I didd.v.push_back(&p1);
and to set the string, I didd.v[0]->setValue<std::string>("BANANA");
-
endbegin over 11 yearsWas wondering what changes I'd have to make to the interface if I wanted a
vector<ParameterBase>
instead of avector<ParameterBase *>
... -
Mooing Duck over 11 years@endbegin: it's not safe to store polymorphic types in a container, so you'd have to come up with something completely different, and far more complicated. I wouldn't go there.
-
lukegravitt almost 7 yearsHow would you implement the
get()
function in the question with this solution? -
Justin Time - Reinstate Monica over 4 yearsIn this case, @lukegravitt,
get()
would likely be implemented by the internal worker classes, withParameter
providing a simple pass-through that roughly looks likepp.get()->get()
orpp->get()
. -
A2LBK about 2 years@MooingDuck Why would a ´static_cast´ not work in your example shown above ? As I see it this would have worked fine, so just wondering the choice of using a ´dynamic_cast´ ?