Member variable template

Consider the following two classes:

class LunchBox { public: std::vector<Apple> m_apples; }; 

and

 class ClassRoom { public: std::vector<Student> m_students; }; 

Classes are similar to each other, since both of them contain a vector-variable-element of objects; however, they do not look like vector objects are different, and member variables have different names.

I would like to write a template that accepts either a LunchBox or ClassRoom as an argument to the template (or some other parameter) and an existing object of the same type (similar to std::shared_ptr ). The template will return an object that adds the member function getNthElement(int i); to improve access to methods. Usage will look like this:

 // lunchBox is a previously initialized LunchBox // object with apples already pushed into m_apples auto lunchBoxWithAccessor = MyTemplate<LunchBox>(lunchBox); auto apple3 = lunchBoxWithAccessor.getNthElement(3); 

I would like to do this without writing specialized templates for each class (which would probably require a member variable to work in some way). Preferably, I don't want to change the LunchBox or ClassRoom . Is it possible to write such a template?

+5
source share
4 answers

You can minimize the amount of code that needs to be written for each class - this should not be a specialization of the template, and it should not be a whole class.

 class LunchBox { public: std::vector<Apple> m_apples; }; class ClassRoom { public: std::vector<Student> m_students; }; // you need one function per type, to provide the member name auto& get_associated_vector( Student& s ) { return s.m_apples; } auto& get_associated_vector( ClassRoom& r ) { return r.m_students; } // and then the decorator is generic template<typename T> class accessor_decorator { T& peer; public: auto& getNthElement( int i ) { return get_associated_vector(peer).at(i); } auto& takeRandomElement( int i ) { ... } // many more ways to manipulate the associated vector auto operator->() { return &peer; } }; LunchBox lunchBox{}; accessor_decorator<LunchBox> lunchBoxWithAccessor{lunchBox}; auto apple3 = lunchBoxWithAccessor.getNthElement(3); 

Overloading a simple helper function should ideally be in the same namespace as the type to create an argument-dependent search operation (for example, Koenig search).

You can also specify an element at the construction point if you prefer to do this:

 template<typename T, typename TMemberCollection> struct accessor_decorator { // public to make aggregate initialization work // can be private if constructor is written T& peer; TMemberCollection const member; public: auto& getNthElement( int i ) { return (peer.*member).at(i); } auto& takeRandomElement( int i ) { ... } // many more ways to manipulate the associated vector auto operator->() { return &peer; } }; template<typename T, typename TMemberCollection> auto make_accessor_decorator(T& object, TMemberCollection T::*member) -> accessor_decorator<T, decltype(member)> { return { object, member }; } LunchBox lunchBox{}; auto lunchBoxWithAccessor = make_accessor_decorator(lunchBox, &LunchBox::m_apples); auto apple3 = lunchBoxWithAccessor.getNthElement(3); 
+5
source

A simple way is to define a feature structure that specializes only with information that makes each case different. Then you have a template class that uses this type of attribute:

 // Declare traits type. There is no definition though. Only specializations. template <typename> struct AccessorTraits; // Specialize traits type for LunchBox. template <> struct AccessorTraits<LunchBox> { typedef Apple &reference_type; static reference_type getNthElement(LunchBox &box, std::size_t i) { return box.m_apples[i]; } }; // Specialize traits type for ClassRoom. template <> struct AccessorTraits<ClassRoom> { typedef Student &reference_type; static reference_type getNthElement(ClassRoom &box, std::size_t i) { return box.m_students[i]; } }; // Template accessor; uses traits for types and implementation. template <typename T> class Accessor { public: Accessor(T &pv) : v(pv) { } typename AccessorTraits<T>::reference_type getNthElement(std::size_t i) const { return AccessorTraits<T>::getNthElement(v, i); } // Consider instead: typename AccessorTraits<T>::reference_type operator[](std::size_t i) const { return AccessorTraits<T>::getNthElement(v, i); } private: T &v; }; 

A few notes:

  • In this case, the implementation will be technically shorter without a feature type; with only Accessor specializations for each type. However, the feature is a good thing to learn, since now you have a way to statically reflect on the LunchBox and ClassRoom in other contexts. Separation of these fragments may be useful.
  • It would be more idiomatic C ++ to use operator[] instead of getNthElement for Accessor . Then you can directly index access objects.
  • AccessorTraits really is not a good name for the type of signs, but I have a problem with something better. These are not traits of accessors, but traits of two other corresponding classes, but what concept even connects these two classes? (Perhaps SchoolRelatedContainerTraits ? It seems laconic ...)
+2
source

You said:

I would like to do this without writing specialized templates for each class

I am not sure why this is a limitation. It is unclear what else you are not allowed to use.

If you are allowed to use multiple function overloads, you can get what you want.

 std::vector<Apple> const& getObjects(LunchBox const& l) { return l.m_apples; } std::vector<Student> const& getObjects(ClassRoom const& c) { return c.m_students; } 

You can write generic code that works with both LaunchBox and ClassRoom without writing any other specialties. However, overloading recording functions is a form of specialization.


Another option would be to upgrade LaunchBox and ClassRoom with

 class LunchBox { public: std::vector<Apple> m_apples; using ContainedType = Apple; }; class ClassRoom { public: std::vector<Student> m_students; using ContainedType = Apple; }; 

and then take advantage of the fact that

 LaunchBox b; std::vector<Apple>* ptr = reinterpret_cast<std::vector<Apple>*>(&b); 

is a legal construct. Then the next class will work fine.

 template <typename Container> struct GetElementFunctor { using ContainedType = typename Container::ContainedType; GetElementFunctor(Container const& c) : c_(c) {} ContainedType const& getNthElement(std::size_t n) const { return reinterpret_cast<std::vector<ContainedType> const*>(&c_)->operator[](n); } Container const& c_; }; 

and you can use it like:

 LunchBox b; b.m_apples.push_back({}); auto f = GetElementFunctor<LunchBox>(b); auto item = f.getNthElement(0); 
+2
source

I made a sample test case using several base classes:

 class Apple { public: std::string color_; }; class Student { public: std::string name_; }; class LunchBox { public: std::vector<Apple> container_; }; class ClassRoom { public: std::vector<Student> container_; }; 

However, for the template function that I wrote, I had to change the name of the containers in each class to fit this, since this is my template function:

 template<class T> auto accessor(T obj, unsigned idx) { return obj.container_[idx]; } 

And here is my main one:

 int main() { LunchBox lunchBox; Apple green, red, yellow; green.color_ = std::string( "Green" ); red.color_ = std::string( "Red" ); yellow.color_ = std::string( "Yellow" ); lunchBox.container_.push_back(green); lunchBox.container_.push_back(red); lunchBox.container_.push_back(yellow); ClassRoom classRoom; Student s1, s2, s3; s1.name_ = std::string("John"); s2.name_ = std::string("Sara"); s3.name_ = std::string("Mike"); classRoom.container_.push_back(s1); classRoom.container_.push_back(s2); classRoom.container_.push_back(s3); for (unsigned u = 0; u < 3; u++) { auto somethingUsefull = accessor(lunchBox, u); std::cout << somethingUsefull.color_ << std::endl; auto somethingElseUsefull = accessor(classRoom, u); std::cout << somethingElseUsefull.name_ << std::endl; } return 0; } 

I'm not sure if there is work to have a different variable name from every other class this function can use; but if there is, I have not figured it out yet. I can continue to work on this to find out if I can improve it; but this is what I have come up with so far.

+1
source

All Articles