模板成员变量
Template a member variable
考虑以下两个 class:
class LunchBox
{
public:
std::vector<Apple> m_apples;
};
和
class ClassRoom
{
public:
std::vector<Student> m_students;
};
classes的相似之处在于它们都包含对象的成员变量向量;但是,它们的不同之处在于向量的对象不同,成员变量具有不同的名称。
我想编写一个模板,将 LunchBox
或 ClassRoom
作为模板参数(或其他一些参数)和相同类型的现有对象(类似于 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编写模板特化(这可能需要指定要以某种方式操作的成员变量)。最好,我不想修改 LunchBox
或 ClassRoom
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 模式是一件好事,因为您现在可以在其他上下文中静态地反映 LunchBox
和 ClassRoom
。解耦这些部分可能很有用。
- 对于
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;
}
您可以编写适用于 LaunchBox
和 ClassRoom
的通用代码,而无需编写任何其他专业化代码。但是,编写函数重载是一种专业化形式。
另一种选择是将 LaunchBox
和 ClassRoom
更新为
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 的变量名不同;但如果有的话我还没有弄清楚。我可以继续努力,看看我是否可以改进它;但这是我到目前为止想出的。
考虑以下两个 class:
class LunchBox
{
public:
std::vector<Apple> m_apples;
};
和
class ClassRoom
{
public:
std::vector<Student> m_students;
};
classes的相似之处在于它们都包含对象的成员变量向量;但是,它们的不同之处在于向量的对象不同,成员变量具有不同的名称。
我想编写一个模板,将 LunchBox
或 ClassRoom
作为模板参数(或其他一些参数)和相同类型的现有对象(类似于 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编写模板特化(这可能需要指定要以某种方式操作的成员变量)。最好,我不想修改 LunchBox
或 ClassRoom
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 模式是一件好事,因为您现在可以在其他上下文中静态地反映LunchBox
和ClassRoom
。解耦这些部分可能很有用。 - 对于
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;
}
您可以编写适用于 LaunchBox
和 ClassRoom
的通用代码,而无需编写任何其他专业化代码。但是,编写函数重载是一种专业化形式。
另一种选择是将 LaunchBox
和 ClassRoom
更新为
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 的变量名不同;但如果有的话我还没有弄清楚。我可以继续努力,看看我是否可以改进它;但这是我到目前为止想出的。