如何扩展一个非值参数包
How to expand a non value paramter pack
我最近一直在尝试用 C++ 扩展一个非值参数包。这可能吗?如果不是,为什么?
我的意思是,如您所见,在注释 //
的行中,给定 TypeMap
class 的参数包,我如何调用 addType<T>()
与每种类型的参数包?提前致谢!
template <typename... T>
class TypeMap
{
using vari_cmps = std::variant<T*...>;
private:
template<typename Type>
void addType()
{
typemap[typeid(Type).name()] = std::make_unique<Type>(0).get();
}
public:
std::map<const char*, vari_cmps> typemap{};
TypeMap()
{
(addType<T,...>()); // Idk how to use this in order to make it work
}
~TypeMap()
{
typemap.clear();
}
};
@HolyBlackCat 已经在评论中回复了,可以这样展开:
TypeMap() {
(addType<T>(), ...);
}
如果 T 是 std::string, int, float
这将扩展为:
TypeMap() {
(addType<std::string>(), addType<int>(), addType<float>());
}
然而,此代码片段中还有一些问题:
1。添加类型()
addType()
不会像您预期的那样工作,因为 unique_ptr 在您将对象放入地图后将其删除。
.get()
仅检索 unique_ptr
管理的指针,但 不会 转移所有权,因此 unique_ptr 仍会删除指向的指针-一旦它超出范围就对象,在你的地图中留下一个悬空的指针。
所以你的 addType()
大致相当于:
template<typename Type>
void addType() {
Type* tptr = new Type(0); // unique pointer constructs object
typemap[typeid(Type).name()] = tptr; // insert pointer value of unique pointer
delete tptr; // unique pointer destructs
}
您可以通过在将 unique_ptr 的值插入映射然后在析构函数中清理它之后释放 unique_ptr 来解决此问题:
template<typename Type>
void addType() {
auto ptr = std::make_unique<Type>(0);
typemap[typeid(Type).name()] = ptr.get();
ptr.release(); // now unique_ptr won't delete the object
}
~TypeMap() {
// cleanup all pointers
for(auto& [name, ptrVariant] : typemap)
std::visit([](auto ptr) { delete ptr; }, ptrVariant);
}
2。考虑使用 std::type_index
而不是 const char*
作为映射键
std::type_info::name()
returns 给定类型的实现定义名称,因此您不能保证给定类型的名称是唯一的。
Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.
另一方面,std::type_index
是专门为此目的构建的 - 使用类型作为键 - 并带有所有比较运算符和 std::hash
专业化,因此您可以将它与 [=27 一起使用=] & std::unordered_map
开箱即用。
例如:
template <class... T>
class TypeMap
{
using vari_cmps = std::variant<T*...>;
private:
template<typename Type>
void addType()
{
typemap[std::type_index(typeid(Type))] = /* something */;
}
public:
std::map<std::type_index, vari_cmps> typemap{};
TypeMap() { /* ... */ }
~TypeMap() { /* ... */ }
template<class U>
U* get() {
auto it = typemap.find(std::type_index(typeid(U)));
return std::get<U*>(it->second);
}
};
考虑使用 std::tuple
std::tuple 基本上是为这个任务构建的,存储任意类型的列表:
例如:
template <class... T>
class TypeMap
{
private:
std::tuple<std::unique_ptr<T>...> values;
public:
TypeMap() : values(std::make_unique<T>(0)...) {
}
template<class U> requires (std::is_same_v<U, T> || ...)
U& get() { return *std::get<std::unique_ptr<U>>(values); }
template<class U> requires (std::is_same_v<U, T> || ...)
U const& get() const { return *std::get<std::unique_ptr<U>>(values); }
};
用法:
TypeMap<int, double, float> tm;
tm.get<int>() = 12;
如果需要,您也可以将 T
直接存储在元组中,避免额外的分配。
我最近一直在尝试用 C++ 扩展一个非值参数包。这可能吗?如果不是,为什么?
我的意思是,如您所见,在注释 //
的行中,给定 TypeMap
class 的参数包,我如何调用 addType<T>()
与每种类型的参数包?提前致谢!
template <typename... T>
class TypeMap
{
using vari_cmps = std::variant<T*...>;
private:
template<typename Type>
void addType()
{
typemap[typeid(Type).name()] = std::make_unique<Type>(0).get();
}
public:
std::map<const char*, vari_cmps> typemap{};
TypeMap()
{
(addType<T,...>()); // Idk how to use this in order to make it work
}
~TypeMap()
{
typemap.clear();
}
};
@HolyBlackCat 已经在评论中回复了,可以这样展开:
TypeMap() {
(addType<T>(), ...);
}
如果 T 是 std::string, int, float
这将扩展为:
TypeMap() {
(addType<std::string>(), addType<int>(), addType<float>());
}
然而,此代码片段中还有一些问题:
1。添加类型()
addType()
不会像您预期的那样工作,因为 unique_ptr 在您将对象放入地图后将其删除。
.get()
仅检索 unique_ptr
管理的指针,但 不会 转移所有权,因此 unique_ptr 仍会删除指向的指针-一旦它超出范围就对象,在你的地图中留下一个悬空的指针。
所以你的 addType()
大致相当于:
template<typename Type>
void addType() {
Type* tptr = new Type(0); // unique pointer constructs object
typemap[typeid(Type).name()] = tptr; // insert pointer value of unique pointer
delete tptr; // unique pointer destructs
}
您可以通过在将 unique_ptr 的值插入映射然后在析构函数中清理它之后释放 unique_ptr 来解决此问题:
template<typename Type>
void addType() {
auto ptr = std::make_unique<Type>(0);
typemap[typeid(Type).name()] = ptr.get();
ptr.release(); // now unique_ptr won't delete the object
}
~TypeMap() {
// cleanup all pointers
for(auto& [name, ptrVariant] : typemap)
std::visit([](auto ptr) { delete ptr; }, ptrVariant);
}
2。考虑使用 std::type_index
而不是 const char*
作为映射键
std::type_info::name()
returns 给定类型的实现定义名称,因此您不能保证给定类型的名称是唯一的。
另一方面,Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.
std::type_index
是专门为此目的构建的 - 使用类型作为键 - 并带有所有比较运算符和 std::hash
专业化,因此您可以将它与 [=27 一起使用=] & std::unordered_map
开箱即用。
例如:
template <class... T>
class TypeMap
{
using vari_cmps = std::variant<T*...>;
private:
template<typename Type>
void addType()
{
typemap[std::type_index(typeid(Type))] = /* something */;
}
public:
std::map<std::type_index, vari_cmps> typemap{};
TypeMap() { /* ... */ }
~TypeMap() { /* ... */ }
template<class U>
U* get() {
auto it = typemap.find(std::type_index(typeid(U)));
return std::get<U*>(it->second);
}
};
考虑使用 std::tuple
std::tuple 基本上是为这个任务构建的,存储任意类型的列表:
例如:
template <class... T>
class TypeMap
{
private:
std::tuple<std::unique_ptr<T>...> values;
public:
TypeMap() : values(std::make_unique<T>(0)...) {
}
template<class U> requires (std::is_same_v<U, T> || ...)
U& get() { return *std::get<std::unique_ptr<U>>(values); }
template<class U> requires (std::is_same_v<U, T> || ...)
U const& get() const { return *std::get<std::unique_ptr<U>>(values); }
};
用法:
TypeMap<int, double, float> tm;
tm.get<int>() = 12;
如果需要,您也可以将 T
直接存储在元组中,避免额外的分配。