在编译时查找 std::map 的重复值

Posted

技术标签:

【中文标题】在编译时查找 std::map 的重复值【英文标题】:Finding duplicate values of std::map at compile time 【发布时间】:2021-09-14 05:24:17 【问题描述】:

我有一个 模板 类 (BiMap),它被用作 双向 地图,用于 查找目的,例如enum 值映射到 std::string 等效项,反之亦然。

要实现这一点,std::string必须也是唯一的,以防止重复的 std::string 值在 按值搜索 查找期间返回第一个找到的 enum 键.

template<typename Key, typename Value>
class BiMap 
 public:
  explicit BiMap(std::initializer_list<std::pair<Key, Value>> &&items) : bimap_items.begin(), items.end() 
    assert(!HasDuplicates(bimap_));
  

  Key GetKeyFromValue(const Value &value) const 
    auto it = std::find_if(bimap_.begin(), bimap_.end(), [&value](const std::pair<Key, Value> &kvp) 
      return kvp.second == value;
    );
    return (it != bimap_.end() ? it->first : Key());
  

  Value GetValueFromKey(const Key &key) const 
    auto it = bimap_.find(key);
    return (it != bimap_.end() ? it->second : Value());
  

 private:
  const std::map<Key, Value> bimap_;
;

我使用一个名为 HasDuplicates 的函数来检查任何重复值:

template<typename Key, typename Value>
bool HasDuplicates(const std::map<Key, Value> &bimap) 
  // Create a map to use the values as keys
  std::map<Value, Key> value_key_map;
  for (auto &kvp : bimap) value_key_map.emplace(kvp.second, kvp.first);

  // If there are no duplicate values then the sizes should be the same
  std::cout << "HasDuplicates: " << std::boolalpha << (value_key_map.size() != bimap.size()) << std::endl;
  return (value_key_map.size() != bimap.size());

我可以运行以下示例代码,它会指示在运行时是否有任何重复值:

// Test 1: No duplicates
std::cout << "**No duplicates test:**" << std::endl;
const BiMap<std::string, int> bi_map_no_dups("foo", 1, "bar", 2, "foobar", 3);
std::cout << "foo: " << bi_map_no_dups.GetValueFromKey("foo") << std::endl;
std::cout << "bar: " << bi_map_no_dups.GetValueFromKey("bar") << std::endl;
std::cout << "foobar: " << bi_map_no_dups.GetValueFromKey("foobar") << std::endl;

// Test 2: Duplicates
std::cout << "**Duplicates test:**" << std::endl;
const BiMap<std::string, int> bi_map_dups("foo", 1, "bar", 2, "foobar", 1);
std::cout << "foo: " << bi_map_dups.GetValueFromKey("foo") << std::endl;
std::cout << "bar: " << bi_map_dups.GetValueFromKey("bar") << std::endl;
std::cout << "foobar: " << bi_map_dups.GetValueFromKey("foobar") << std::endl;

这样的输出是:

**No duplicates test:**
HasDuplicates: false
foo: 1
bar: 2
foobar: 3
**Duplicates test:**
HasDuplicates: true
main.cpp:22: BiMap<Key, Value>::BiMap(std::initializer_list<std::pair<_T1, _T2> >&&) [with Key = std::basic_string<char>; Value = int]: Assertion `!HasDuplicates(bimap_)' failed.

上述代码的工作示例可以在here找到。

问题:

如何评估std::map编译时是否有重复值?

我尝试过的:

我已经尝试实现constexpr模板函数,如here:

template <typename K, typename V> constexpr bool has_duplicates(const std::map<K,V> *map)

    std::map<V,K> value_key_map;
    for(auto &kvp : map) value_key_map.emplace(map->second,map->first);
    return map->size() == value_key_map.size();


int main() 
 // Cannot get this part to work
 constexpr std::map<std::string, int> bimap("foo", 1, "bar", 2, "foobar", 1);
 static_assert(!has_duplicates(&bimap));

 return 0;

注意:我正在使用 C++11,我还不能声明 local 变量和 循环在 constexpr 函数内部,因此应该恢复为递归,如 here 所示。但是,对于这个例子,如果我能找到一个具有 C++14 的 constexpr 功能的合适解决方案,我会很高兴,并且稍后我会得到一个递归版本(如果可能的话)。

【问题讨论】:

std::mapstd::string 不是 constexprstd::string 可能仅在 C++20 后用于 constexpr 函数)。 如果你变成std::array&lt;std::pair&lt;const K, V&gt;, N&gt;(实际上是你自己的数组,因为在C++11中缺少constexpr),你可以在编译时进行检查。 @Jarod42 你是完全正确的。这似乎是不可能的。但是,我确实使用了您的 std::array 提案here,它是仅使用 GCC10.x 及更高版本的 C++11 和 C++14 的可行版本。 请注意,您实际上需要自定义函数来比较 C 字符串。 "FOO" == "FOO" 不保证是true @Jarod42,非常感谢。根据您的最后一条评论,我能够创建一个合适的 solution,它支持 GNU 4.8.5 和 C++11 以及 C++14 及更高版本的新编译器。 【参考方案1】:

如何评估 std::map 在编译时是否有重复值?

你不能。 std::map() 在编译时不可用。

您可以改为使用 https://github.com/serge-sans-paille/frozen 或 https://github.com/mapbox/eternal 进行检查(或其他一些 constexpr 结构)。

另一种方法(如果您想保持在 C++11 级别)是构建一个基于模板元编程的映射,例如 this answer。

【讨论】:

感谢您为我指明正确的方向。冰冻的和永恒的看起来都很有希望。不幸的是,我被 C++11 和 GCC 4.8.5 困住了,这使得两者在此时都不可行。我将尝试模板元编程选项,看看这是否能在我当前的限制下解决我的问题。【参考方案2】:

comment 部分的帮助下,我能够为我的问题制定合适的解决方案。

重要提示std::map 不是 constexpr,并且不能用于在编译时评估其内容。

但是,您可以使用std::array&lt;std::pair&lt;const K, V&gt;, N&gt; 来帮助在编译时从 C++14 评估内容,如下所示:

template<typename K, typename T, size_t N>
constexpr bool has_duplicates(const std::array<std::pair<K, T>, N> *arr) 
  for (int i = 0; i < N - 1; i++) 
    for (int j = i + 1; j < N; j++) 
      if (compare_equal((*arr)[i].second,(*arr)[j].second) || 
          compare_equal((*arr)[i].first, (*arr)[j].first)) return true;
    
  
  return false;

有用法:

constexpr std::array<std::pair<int, const char *>, 3> arrd 1, "foobar",
                                                             2, "bar",
                                                             3, "foobar";
static_assert(!has_duplicates(&arrd), "Duplicates Found!");

并且您需要以下额外的比较函数:

template<typename A, typename B>
constexpr bool compare_equal(A a, B b) 
    return a == b;


template <>
constexpr bool compare_equal(const char * a, const char * b) 
    return *a == *b && (*a == '\0' || compare_equal(a + 1, b + 1));

对于 C++11 支持,您需要将 has_duplicates 函数更改为 recursive 实现。这是两个版本的完整工作 example。

因此,您可以在您的类中使用它来在编译时检查重复值。

【讨论】:

以上是关于在编译时查找 std::map 的重复值的主要内容,如果未能解决你的问题,请参考以下文章

C++,我可以在编译时静态初始化 std::map 吗?

CLion无法解析类型std :: unordered_map,即使它提示我包含标题和编译工作

GTEST 中 std::map 和 dense_hash_map 的编译器错误

在 std::map 中查找最接近或准确的键

CLion 无法解析类型 std::unordered_map 即使它提示我包含标题并且编译工作

将 std::map 转换为有序的 std::vector