如何使用模板根据类型将元素添加到各种容器中

How to add element to various container depending of type using template

本文关键字:添加 元素 何使用 类型      更新时间:2023-10-16

我有一个相当愚蠢的问题,但我希望你能帮助我。

我有一个具有多个向量的类,这些向量具有不同的存储类型。

class BaseClass{
std::string Name;
}
class B : public BaseClass{
}
class C : public BaseClass{
}
class A{
vector<std::pair<std::string, B>> V1;
vector<std::pair<std::string, C>> V2;
}

在我的类 A 中,有一个模板函数可以向此向量添加元素:

template <class T>  void addElement(T Obj);

我希望这种情况发生:

A a;
B b;
C c;

a.addElement<B>(b) -> then element b is added to vector V1
a.addElement<C>(c) -> then element c is added to vector V2

我想出了这样的东西:

template <class T>  void addElement(T Obj){
std::pair<std::string, T> AddedPair(Obj.Name, Obj);
if (typeid(T) == typeid(B)) 
V1.push_back(AddedPair);
if (typeid(T) == typeid(C)) 
V2.push_back(AddedPair);
}

但不幸的是,这段代码无法编译,因为以某种方式将模板编译为一个整体,如果我使用 B 作为模板参数,那么编译器无法将 B 转换为 C,尽管该程序永远不会达到这种转换可能发生的程度:(

您有什么建议如何解决此问题吗?我会非常伟大。

而不是有

template <class T>  void addElement(T Obj);

只需重载函数即可。 那会给你

void addElement(const B& Obj)
{
V1.push_back({Obj.Name, Obj});
}
void addElement(const C& Obj)
{
V2.push_back({Obj.Name, Obj});
}

这为您节省了专用模板或需要 C++17 和if constexpr在编译时做出决定的所有语法。


原因

template <class T>  void addElement(T Obj){
std::pair<std::string, T> AddedPair(Obj.Name, Obj);
if (typeid(T) == typeid(B)) 
V1.push_back(AddedPair);
if (typeid(T) == typeid(C)) 
V2.push_back(AddedPair);
}

每个 if 块中的代码需要有效(即使永远无法访问(,但不能,因为您将在向量中添加不同类型的代码。if constexpr有帮助,但我发现重载同样多,并且使代码不向后兼容。

这意味着您必须像

template <class T>  void addElement(T Obj);
template <>  void addElement(B Obj)
{
V1.push_back({Obj.Name, Obj});
}   
template <>  void addElement(C Obj)
{
V1.push_back({Obj.Name, Obj});
}

或使用if constexpr

template <class T>  void addElement(T Obj){
std::pair<std::string, T> AddedPair(Obj.Name, Obj);
if constexpr(std::is_same_v<T, B>) 
V1.push_back(AddedPair);
if constexpr(std::is_same_v<T, C>) 
V2.push_back(AddedPair);
}       

这可能是标记元组库的一个用例。它使按关联类型为容器编制索引成为可能。因此,处理数十个相似vector<std::pair<std::string, B>> V1;字段的代码变得通用:

#include <vtt/container/Tagged Tuple.hpp>
#include <string>
#include <type_traits>
#include <vector>
#include <utility>
class BaseClass{::std::string Name;};
class B : public BaseClass{};
class C : public BaseClass{};
class A
{
public: template<typename x_Item> using
t_Vector = ::std::vector<::std::pair<::std::string, x_Item>>;
public: using
t_Vectors = ::n_vtt::n_container::t_TaggedTuple
<// index type -> value type mapping
B, t_Vector<B>
,   C, t_Vector<C>
>;
private: t_Vectors m_vectors;
public: template<typename x_Item> void
Add_Item(x_Item && item)
{
m_vectors
// invoke either Get_MutableItem<B> or Get_MutableItem<C>
.Get_MutableItem<::std::remove_reference_t<::std::remove_cv_t<x_Item>>>()
// add item into corresponding std::vector
.emplace_back(::std::string{}, ::std::forward<x_Item>(item));
}
};
int main()
{
A a;
a.Add_Item(B{});
C c{};
a.Add_Item(c);
return 0;
}

你可以使用"generic getter"作为你的向量:

class A
{
public:
template <typename T>
std::vector<std::pair<std::string, T>>& getVector() {
auto vectors = std::tie(V1, V2);
return std::get<std::vector<std::pair<std::string, T>>&>(vectors);
}
template <class T>
void addElement(T Obj) {
getVector<T>().emplace_back(Obj.Name, Obj);
}
std::vector<std::pair<std::string, B>> V1;
std::vector<std::pair<std::string, C>> V2;
};

更改成员资格可能有意义,直接使用std::tuple。 您可能希望模板化整个类:

template <typename ... Ts>
class A_Impl
{
private:
template <typename T>
decltype(auto) getVector() const {
return std::get<std::vector<std::pair<std::string, T>>>(Vs);
}
template <typename T>
decltype(auto) getVector() {
return std::get<std::vector<std::pair<std::string, T>>>(Vs);
}
public:
template <class T>
void addElement(T Obj) {
getVector<T>().emplace_back(Obj.Name, Obj);
}
private:
std::tuple<std::vector<std::pair<std::string, Ts>>...> Vs;
};
using A = A_Impl<B, C>;

如果你打算在几个地方使用你的向量,你可以专门化一个模板来获得一次正确的向量,那么你的主模板代码可以是泛型的:

class A{
public:
template < typename T >
void addElement(T obj)
{
getVector<T>().push_back(std::make_pair(obj.Name,obj));
}
template < typename T >
T& getElement(size_t index)
{
return getVector<T>().at(index).second;
}
private:
vector<std::pair<std::string, B>> V1;
vector<std::pair<std::string, C>> V2;
template < typename T >
vector<std::pair<std::string, T>>& getVector();
};
template <>
vector<std::pair<std::string, B>>& A::getVector<B>() { return V1; }
template <>
vector<std::pair<std::string, C>>& A::getVector<C>() { return V2; }

如果你像这样使用共享的 BaseClass 会怎样? 您必须为 BaseClass 创建一个通用接口,我不确定 B 和 C 在功能方面会有多少不同。

class BaseClass{
public:
std::string Name;
};
class B : public BaseClass{
};
class C : public BaseClass{
};
class A{
public:
std::vector<std::pair<std::string, std::unique_ptr<BaseClass>> > V1;
std::vector<std::pair<std::string, std::unique_ptr<BaseClass>> > V2;
template <class T>  void addElement(T Obj)
{
std::pair<std::string, std::unique_ptr<T>> AddedPair(Obj.Name, std::make_unique<T>(Obj));
if (typeid(T) == typeid(B)) 
V1.push_back(AddedPair);
else if (typeid(T) == typeid(C)) 
V2.push_back(AddedPair);
}  
};
int main()
{
A a;
B b;
C c;
a.addElement<B>(b) ;//-> then element b is added to vector V1
a.addElement<C>(c) ;//-> then element c is added to vector V2
}