简体   繁体   English

模板成员变量

[英]Template a member variable

Consider the following two classes: 考虑以下两个类:

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

and

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

The classes are alike in that they both contain a member variable vector of objects; 这些类是相似的,因为它们都包含对象的成员变量向量; however, they are unalike in that the vector's objects are different and the member variables have different names. 然而,它们是不相似的,因为向量的对象是不同的,并且成员变量具有不同的名称。

I would like to write a template that takes either LunchBox or ClassRoom as a template argument (or some other parameter) and an existing object of the same type (similar to a std::shared_ptr ). 我想编写一个模板,它将LunchBoxClassRoom作为模板参数(或其他一些参数)和相同类型的现有对象(类似于std::shared_ptr )。 The template would return an object that adds a getNthElement(int i); 模板将返回一个添加getNthElement(int i); member function to improve accessing the methods. 成员函数,以改善访问方法。 Usage would be like: 用法如下:

// 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 template specializations for each class (which likely would require specifying the member variable to operate on in some way). 我想这样做而不为每个类编写模板特化(这可能需要指定成员变量以某种方式操作)。 Preferably, I do not want to modify the LunchBox or ClassRoom classes. 最好,我不想修改LunchBoxClassRoom类。 Is writing such a template possible? 写这样的模板有可能吗?

You can minimize the amount of code that has to be written for each class -- it doesn't have to be a template specialization and it doesn't have to be an entire 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);

The simple helper function overload should ideally be in the same namespace as the type, to make argument-dependent lookup work (aka Koenig lookup). 理想情况下,简单的辅助函数重载应该与类型在同一名称空间中,以使依赖于参数的查找工作(也称为Koenig查找)。

It's also possible to specify the member at the point of construction, if you prefer to do that: 如果您愿意,也可以在构造点指定成员:

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);

A simple way to do this is define a trait struct that has specializations with just the information that makes each case different. 一种简单的方法是定义一个特征结构,该结构具有仅具有使每个情况不同的信息的特化。 Then you have a template class that uses this traits type: 然后你有一个使用这个traits类型的模板类:

// 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 would technically be shorter without a traits type; 在这种情况下,在没有特征类型的情况下,技术上的实施将更短; with only specializations of Accessor for each type. 只有每种类型的Accessor However, the traits pattern is a good thing to learn as you now have a way to statically reflect on LunchBox and ClassRoom in other contexts. 但是,特征模式是一件好事,因为您现在可以在其他上下文中静态地反映LunchBoxClassRoom Decoupling these pieces can be useful. 解耦这些部分可能很有用。
  • It would be more idiomatic C++ to use operator[] instead of getNthElement for Accessor . 对于Accessor ,使用operator[]而不是getNthElement会更加惯用C ++。 Then you can directly index the accessor objects. 然后,您可以直接索引访问者对象。
  • AccessorTraits really isn't a good name for the traits type, but I'm having trouble coming up with anything better. AccessorTraits确实不是特征类型的好名字,但是我无法想出更好的东西。 It's not the traits of the accessors, but the traits of the other two relevant classes -- but what concept even relates those two classes? 这不是访问者的特征,而是其他两个相关类的特征 - 但是这两个类的概念甚至是什么? (Perhaps SchoolRelatedContainerTraits ? Seems a bit wordy...) (也许是SchoolRelatedContainerTraits ?看起来有点罗嗦......)

You said: 你说:

I would like to do this without writing template specializations for each class 我想在不为每个类编写模板特化的情况下这样做

I am not sure why that is a constraint. 我不确定为什么这是一个约束。 What is not clear is what else are you not allowed to use. 不清楚的是你还有什么不允许使用的。

If you are allowed to use couple of 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 specializations. 您可以编写适用于LaunchBoxClassRoom通用代码,而无需编写任何其他专门化。 However, writing function overloads is a form of specialization. 但是,编写函数重载是一种特殊化的形式。


Another option will be to update LaunchBox and ClassRoom with 另一个选项是使用更新LaunchBoxClassRoom

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 following 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 as: 你可以用它作为:

LunchBox b;
b.m_apples.push_back({});

auto f = GetElementFunctor<LunchBox>(b);
auto item = f.getNthElement(0);

I did a test case sample using a few basic 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 did however have to change the name of the containers in each class to match for this to work as this is my template function: 但是对于我写的模板函数,我必须更改每个类中容器的名称以匹配此工作,因为这是我的模板函数:

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

And this is what my main looks like: 这就是我的主要看法:

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 a work around to have a different variable name from each different class this function can use; 我不确定是否有一个解决方法是从这个函数可以使用的每个不同类中获得一个不同的变量名; but if there is I haven't figured it out as of yet. 但是如果有的话我还没想到它。 I can continue to work on this to see if I can improve it; 我可以继续研究这个问题,看看能不能改进它; but this is what I have come up with so far. 但这是我到目前为止所提出的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM