C++ class with template member variable

85,582

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.

Share:
85,582
endbegin
Author by

endbegin

the end is a beginning.

Updated on July 14, 2022

Comments

  • endbegin
    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
    Mooing Duck over 11 years
    I 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
    aschepler over 11 years
    This makes it awfully easy to accidentally store a reference to temporary in a Diagram.
  • pmr
    pmr over 11 years
    @aschepler Thanks. I thought about that while coding. Added remove_reference.
  • endbegin
    endbegin over 11 years
    Thanks 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
    pmr over 11 years
    @MooingDuck Adding a virtual copy to ParameterBase 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
    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
    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
    endbegin over 11 years
    Had to make some minor changes to your test code, but this works well. Thanks.
  • Mooing Duck
    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
    Mooing Duck over 11 years
    @endbegin: as a note, this is almost exactly how boost::any works.
  • endbegin
    endbegin over 11 years
    Nothing major: Initialized a diagram type Diagram d; and a parameter Parameter<std::string> p1("Hello"); To add to the vector, I did d.v.push_back(&p1); and to set the string, I did d.v[0]->setValue<std::string>("BANANA");
  • endbegin
    endbegin over 11 years
    Was wondering what changes I'd have to make to the interface if I wanted a vector<ParameterBase> instead of a vector<ParameterBase *> ...
  • Mooing Duck
    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
    lukegravitt almost 7 years
    How would you implement the get() function in the question with this solution?
  • Justin Time - Reinstate Monica
    Justin Time - Reinstate Monica over 4 years
    In this case, @lukegravitt, get() would likely be implemented by the internal worker classes, with Parameter providing a simple pass-through that roughly looks like pp.get()->get() or pp->get().
  • A2LBK
    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´ ?