使用模板作为 std::map 中的键

Using templates as keys in a std::map

本文关键字:map std      更新时间:2023-10-16

我想知道是否可以使用模板作为地图的键。 例如:

std::map< <T> , Node> nodes;

从本质上讲,我想做的是能够拥有一堆包含任意类型数据的节点,并由该数据键控。 我想我可以通过将所述数据转换为二进制并以此进行键控来做到这一点,但这很混乱,我想避免它。

澄清一下,我希望能够使用任何类型的变量作为键。 例如,如果我有 2 个节点,一个包含 int 作为其数据,另一个包含 Foo 作为其数据,我希望能够将它们的数据作为键放在同一个映射中。 也许地图不是我想要的,我不确定...

思潮? 谢谢!

如果您没有明确禁用 RTTI,请参考 nogard 的答案。标准类型 ID 保证对于整个 DLL 中的整个程序是唯一的。对于函数的地址,情况并非如此。

我通常做的是这样的:

template<typename T>
void type_id(){}
using type_id_t = void(*)();

然后,我像这样使用它:

std::map<type_id_t, Node> nodes;
nodes[type_id<AType>] = Node{...};
nodes[type_id<BType>] = Node{...};

当然,这可以通过 C++14 的变量模板来增强。


对不起,我只是重新阅读了这个问题,我更好地理解了它。

你想要的是std::any,但它只有C++17。您可以改用boost::any

它将看起来像这样:

std::map<std::any, Node> nodes;
nodes.emplace("string as key", Node{});
nodes.emplace(23, Node{});

只要地图可以以某种方式对std::any实例进行排序,它就应该工作。如果没有,您可以改用哈希映射:

std::unordered_map<std::any, Node> nodes;

然后,只要地图可以散列任何,它就会起作用。

我认为为此目的,将std::type_info用于以下类型要容易得多:

std::map<std::type_info, std::string> m;
m[typeid(int)] = "integer";

但这真的取决于你想要实现什么,我仍然不清楚。希望有帮助

至少如果我明白你想要什么,简短的回答是否定的。

映射中的键必须按顺序排列,也就是说,必须为任何键对 A 和 B 定义,必须定义有序关系,其中 A 小于 B,或 B 小于 A,或者两个键等效。

给定两个完全任意类型的键,不会有定义的方式来比较它们。因此,不能将它们用作地图的键。

为了接近某些东西,您需要定义一些要支持的特定类型集。然后,您可以定义一些(大致)是您想要支持的所有类型的联合的东西。仅凭这一点是不够的 - 您还必须定义排序。根据您要完成的操作,您可能(例如)在每个对象中都有一个 ID,并按 ID 对它们进行排序。或者,您可以定义对象之间的排序,以便(例如)每个Person在每次Dog之前排序,在每次Dream之前排序,依此类推。然后,您必须像往常一样在每种类型中定义一个排序。

然而,我要警告的是,这通常涉及相当多的额外工作,并且提供的回报很少。我想说的是,在我看到人们(尝试)这样做的90%以上的时间里,这是一个错误,充其量效果不佳。如果可能的话,我会尝试找到一些其他方法来解决您要解决的任何问题。

您可以创建一个类似于poly_key的类,它将接受任何类型,前提是它是:

  • 复制或可移动(此演示可复制)

  • 相等可比(如果在无序列图中使用)

  • 低于可比性(如果在地图中使用)

  • 可哈希(如果在有序映射中使用)

  • o可流式传输(对于此演示)

这样:

#include <iostream>
#include <string>
#include <memory>
#include <unordered_map>
#include <map>
#include <typeinfo>
/// a non-polymorphic container for a polymorphic key type
struct poly_key
{
/// concept defines the key's capabilities
struct concept {
virtual bool equal(const void* other) = 0;
virtual bool less(const void* other) = 0;
virtual const void* address() const = 0;
virtual const std::type_info& type() const = 0;
virtual std::size_t hash() const = 0;
virtual std::ostream& emit(std::ostream&) const = 0;
virtual std::unique_ptr<concept> clone() const = 0;
virtual ~concept() = default;
};
using ptr_type = std::unique_ptr<concept>;
/// model<> models the concept for any key which supports the required operations
template<class T>
struct model : concept {
model(T&& t) : _t(std::move(t)) {}
bool equal(const void* other) override {
return _t == (*reinterpret_cast<const T*>(other));
}
bool less(const void* other) override {
return _t < (*reinterpret_cast<const T*>(other));
}
const void* address() const override {
return std::addressof(_t);
}
const std::type_info& type() const override {
return typeid(_t);
}
std::size_t hash() const override {
return std::hash<T>()(_t);
}
std::ostream& emit(std::ostream& os) const override
{
return os << _t;
}
virtual std::unique_ptr<concept> clone() const override
{
return std::make_unique<model>(*this);
}

T _t;
};
template<class T>
poly_key(T t) : _impl(std::make_unique<model<T>>(std::move(t))) {}
std::size_t hash() const {
return _impl->hash();
}
bool operator==(const poly_key& r) const {
return _impl->type() == r._impl->type()
&& _impl->equal(r._impl->address());
}
bool operator<(const poly_key& r) const {
auto& lt = _impl->type();
auto& rt = r._impl->type();
if (lt.before(rt)) {
return true;
}
else if (rt.before(lt)) {
return false;
}
else {
return _impl->less(r._impl->address());
}
}
poly_key(const poly_key& r)
: _impl(r._impl->clone())
{
}
poly_key(poly_key&& r)
: _impl(std::move(r._impl))
{
}
friend std::ostream& operator<<(std::ostream& os, const poly_key& k)
{
return k._impl->emit(os);
}
ptr_type _impl;
};
/// make it hashable
namespace std {
template<> struct hash<::poly_key> {
bool operator()(const ::poly_key& r) const {
return r.hash();
}
};
}
//
// test
//
int main()
{
std::unordered_map<poly_key, std::string> m;
m.emplace(poly_key(std::string("key 1")), "Hello");
m.emplace(poly_key(2), "World");
std::cout << "unordered:n";
for (auto& e : m) {
std::cout << e.first << " : " << e.second << std::endl;
}
std::cout << "nordered:n";
std::map<poly_key, std::string> m2 (m.begin(), m.end());
for (auto& e : m2) {
std::cout << e.first << " : " << e.second << std::endl;
}   
}

示例输出(顺序可能因工具集而异):

unordered:
2 : World
key 1 : Hello
ordered:
key 1 : Hello
2 : World

为您的类型命名并将其用于地图:

#include<map>
#include<cassert>
struct B { static int cnt; };
int B::cnt = 0;
template<typename T>
struct D: B { static const int type; };
template<typename T>
const int D<T>::type = B::cnt++;
std::map<int, int> values;
template<typename T>
void set(int value) { values[D<T>::type] = value; }
template<typename T>
int get() { return values[D<T>::type]; }
struct T1 { };
struct T2 { };
int main() {
set<T1>(42);
set<T2>(0);
assert(get<T1>() == 42);
assert(get<T2>() == 0);
set<T2>(3);
assert(get<T2>() == 3);
}
相关文章: