【问题标题】:Overload std::map with different key type用不同的键类型重载 std::map
【发布时间】:2018-08-01 13:43:42
【问题描述】:

所以我有以下 STL std::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++ 中做到这一点吗?

PS:我知道有些代码在重载中可能是多余的

【问题讨论】:

  • 您可以为std::map::find() 执行此操作,但您不能为std::map::operator[] 执行此操作,因此您可以创建临时Type 或仅使用find
  • 听起来你只需要提供Type 采用任何一种类型的构造函数。那么挑战可能是使它们具有适当的可比性。
  • @FrançoisAndrieux 这对于 std::map::find() 自 c++14 以来不是必需的
  • @Slava 我不确定您的评论中的 "that" 指的是什么。
  • @FrançoisAndrieux by "that" 我的意思是您不必创建 Type 的实例即可将其传递给 find

标签: c++ stl


【解决方案1】:

您需要的称为异构查找,自 C++14 起由 std::map 支持。为此,您可以定义自己的比较器结构并在其中提供类型 is_transparent 以启用此功能。关于如何启用异构查找的详细信息可以在这里找到How can I search an std::map using a key of a different type

还要注意,虽然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);

无论如何你都应该这样做,因为你会做两次查找而不是一次,这在地图上的操作非常昂贵。

所以对于你的情况比较器应该是这样的:

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&lt;&gt; 作为第三个参数提交给std::map,但在这种情况下,您缺少比较运算符,它将std::string 作为左操作数,Type 作为右操作数,因为比较运算符不能成为Type 的成员我认为通过单独的比较器来做这件事更干净(代码一致)。

【讨论】:

  • 我不知道这是可能的。自 C++11 以来,我有很多事情要做。
【解决方案2】:

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

  1. Type 类不支持小于运算,这在用作 std::map 键类型时是必需的。
  2. 字符串(字符数组常量)用在两个需要类型的地方:map::find()map::operator[] 的参数。编译器无法从 const char* 转换为 Type。

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

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

其次,定义如何使用单参数 ctor 将 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)语言功能。

【讨论】:

    【解决方案3】:

    试试这个:

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

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

    std::map 的默认第三个模板参数是std::less&lt;key_type&gt;,这是比较有限的。为了向后兼容 C++14 之前的代码,这种方式保持不变,但许多新代码最好使用不带模板参数的 std::less

    【讨论】:

    • 你确定吗?这不编译。你在哪里找到的?
    • 你实际上需要std::less&lt;&gt;
    • @n.m. std::less&lt;void&gt; 似乎确实启用了异构查找 en.cppreference.com/w/cpp/utility/functional/less_void
    • @Slava 是的,功能就在这里,只是语法有点不同。
    猜你喜欢
    • 2015-11-02
    • 1970-01-01
    • 2011-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-19
    • 1970-01-01
    相关资源
    最近更新 更多