重载std::映射不同的密钥类型

Overload std::map with different key type

本文关键字:密钥 类型 std 映射 重载      更新时间:2023-10-16

所以我有以下STLstd::map容器

#include <map>
#include <vector>
// ...
class Type
{
std::string key;
int support;
};
std::map<Type, std::vector<int> > index;

我想重载地图,这样下面的两个if子句都有效:

int main()
{
std::map<Type, std::vector<int> > index;
Type type1;
type1.key = "test";
type1.support = 10;
index[type1] = std::vector<int>();
if (index.find(type1) != index.end())
{
index[type1].push_back(0);
}
// I can not make this work
if (index.find("test") != index.end())
{
index["test"].push_back(0);
}

return 0;
}

我试过这些过载:

class Type 
{
public:
std::string key;
int support;
size_t operator()() const
{
return std::hash<std::string>{}(name);
}
bool operator==(const struct Type& obj) const 
{
return (key == obj.key);
}
bool operator<(const struct Type& obj) const 
{
return key < obj.key;
}
bool operator<(const std::string& other_key) const 
{
return key < other_key;
}

bool operator==(const std::string& other_key) const
{
return other_key == key;
}

};
namespace std
{
template<>
struct hash<Type>
{
size_t operator()(const Type& obj) const
{
return obj();
}
// Specialization here does not seem to work
size_t operator()(const std::string& name) const
{
return std::hash<std::string>{}(name);
}
};
template<>
struct less<Type>
{
bool operator() (const std::string& lname, const std::string& rname)
{
return lname < rname;
}
};

由于在我的模型中,std::string key字段唯一定义了类型,我如何重载std::map容器,以便对容器的项进行索引?我能在C++中做到这一点吗?

附言:我知道一些代码可能是多余的过载

您需要的是称为异构查找,并且自C++14以来由std::map支持。为此,您可以定义自己的比较器结构,并在其中提供类型is_transparent以启用此功能。关于如何启用异构查找的详细信息,可以在这里找到。如何使用不同类型的键搜索std::map

另外请注意,虽然std::map::find()支持它,但std::map::operator[]不支持,因此您必须替换您的代码:

if (index.find("test") != index.end())
{
index["test"].push_back(0);
}

类似于:

auto it = index.find("test");
if( it != index.end()) 
it->second.push_back(0);

无论如何,您都应该这样做,因为您将进行两次查找,而不是一次查找,这在map上是非常昂贵的操作。

因此,对于您的案例,比较器应该类似于:

struct CompareType
{
using is_transparent = std::true_type;
// standard comparison (between two instances of Type)
bool operator()(const Type& lhs, const Type& rhs) const { return lhs.key < rhs.key; }
// comparisons btw Type and std::string
bool operator()( const Type& lhs, const std::string &rhs) const { return lhs.key < rhs; }
bool operator()(const std::string &lhs, const Type& rhs) const { return lhs < rhs.key; }
};

然后你创建你的地图:

std::map<Type, std::vector<int>,CompareType> index;

并且您不再需要Type本身的中的比较方法

另一种方法是将std::less<>作为第三个参数提交给std::map,但在这种情况下,您缺少比较运算符,它将std::string作为左操作数,将Type作为右操作数,并且由于该比较运算符不能是Type的成员,我认为通过单独的比较器进行比较更干净(那里的代码是一致的(。

我在提供的代码中看到两个问题:

  1. Type类不支持小于运算,当用作std::map<>时需要小于运算密钥类型
  2. 字符串(char数组常量(用于两个需要Type的位置:map::find()map::operator[]的参数。编译器无法从const char*转换为Type

首先,满足类型作为std::map<>的关键类型的要求:

// Define comparison/ordering of two Type instances
bool operator<(const Type& a, const Type& b)
{
return a.key < b.key;
}

其次,定义如何使用单个参数将const char*(例如"test"(隐式转换为Type:

class Type 
{
public:
// Implicitly converts const char* value to instance of Type.
Type(const char* k) : key(k), support(0) {}
//...
};

这不需要任何更新的(C++11或C++14(语言功能。

简单地尝试一下:

std::map<Type, std::vector<int>, std::less<> > index;

这需要C++14。请注意,使用不带模板参数的std::less,这允许搜索"兼容"密钥,而不仅仅是精确的key_type。这就是您需要通过自定义比较函数"解锁"find()的使用。

std::map的默认第三个模板参数是std::less<key_type>,这是更有限的。为了与C++14之前的代码保持向后兼容性,它仍然采用这种方式,但许多新代码最好在没有模板参数的情况下使用std::less