模板成员变量

Template a member variable

考虑以下两个 class:

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

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

classes的相似之处在于它们都包含对象的成员变量向量;但是,它们的不同之处在于向量的对象不同,成员变量具有不同的名称。

我想编写一个模板,将 LunchBoxClassRoom 作为模板参数(或其他一些参数)和相同类型的现有对象(类似于 std::shared_ptr).该模板将 return 一个添加 getNthElement(int i); 成员函数以改进对方法的访问的对象。用法如下:

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

我想为每个class编写模板特化(这可能需要指定要以某种方式操作的成员变量)。最好,我不想修改 LunchBoxClassRoom classes。 这样的模板可以写吗?

一个简单的方法是定义一个特征结构,它具有专门化的信息,使每个案例都不同。然后你有一个模板 class 使用这个特征类型:

// 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;
};

一些注意事项:

  • 在这种情况下,如果没有特征类型,实施在技术上会更短;每种类型只有 Accessor 的特化。但是,学习 traits 模式是一件好事,因为您现在可以在其他上下文中静态地反映 LunchBoxClassRoom。解耦这些部分可能很有用。
  • 对于 Accessor,使用 operator[] 而不是 getNthElement 会更符合 C++ 的习惯。然后你可以直接索引访问器对象。
  • AccessorTraits 确实不是特征类型的好名字,但我很难想出更好的名字。这不是访问器的特征,而是其他两个相关 classes 的特征——但是什么概念甚至与这两个 classes 相关? (大概是SchoolRelatedContainerTraits?好像有点罗嗦...)

您可以最大限度地减少必须为每个 class 编写的代码量——它不必是模板专业化,也不必是整个 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);

理想情况下,简单的辅助函数重载应该与类型位于相同的命名空间中,以使参数相关的查找工作(又名 Koenig 查找)。

如果您愿意,也可以在构造时指定成员:

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

你说:

I would like to do this without writing template specializations for each class

我不确定为什么这是一个约束。不清楚的是您还不允许使用什么。

如果你被允许使用几个函数重载,你可以得到你想要的。

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

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

您可以编写适用于 LaunchBoxClassRoom 的通用代码,而无需编写任何其他专业化代码。但是,编写函数重载是一种专业化形式。


另一种选择是将 LaunchBoxClassRoom 更新为

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

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

然后,利用

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

是一个合法的结构。然后,以下 class 将正常工作。

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_;
};

您可以将其用作:

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

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

我使用一些基本的 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_;
};

然而,对于我编写的模板函数,我确实必须更改每个 class 中容器的名称以使其匹配,因为这是我的模板函数:

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

这就是我的主图:

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

我不确定是否有变通方法可以让此函数可以使用的每个不同 class 的变量名不同;但如果有的话我还没有弄清楚。我可以继续努力,看看我是否可以改进它;但这是我到目前为止想出的。