函数模板和模板参数不明确

3

我有几个std::map<some_type, some_other_type>,我正在尝试编写下面展示的函数模板Lookup

当键为指针或标量时,函数模板可以正常工作,但是如果键是std::string则会出现问题。

#include <iostream>
#include <map>

// Usage :
//   bool valueisinmap = Lookup(themap, thekey, thevalue);
template <typename TK, typename TV>
bool Lookup(std::map<TK, TV>& map,  TK key, TV& value)
{
  auto it = map.find(key);
  if (it != map.end())
  {
    value = it->second;
    return true;
  }
  else
  {
    return false;
  }
}

int main()
{
    std::map<std::string, std::string> m;
    m.insert(std::make_pair("2", "two"));

    std::string x;
    std::string key = "2";

    if (Lookup(m, key, x))
      std::cout << "OK\n";

    if (Lookup(m, "2", x))  // problem here
      std::cout << "OK\n";
}

我理解为什么Lookup(m, "2", x)无法编译,因为"2"的类型不是std::string,但是否有办法编写函数模板,使我既可以使用Lookup(m, "2", x),也可以使用Lookup(m, key, x),其中keystd::string
如果可以,这就引发了第二个问题:
bool Lookup(std::map<TK, TV>& map,  TK key, TV& value)

key 是按值传递的,如果其类型是 std::string,则会进行拷贝。有没有一种方法可以通过引用(或某些 C++14 的黑科技)来传递 key,并且仍然能够使用 Lookup(m, "2", x)

3个回答

3

解决此问题的一种方法是引入一个单独的类型参数用于键类型,如下所示:

template <typename TKM, typename TK, typename TV>
bool Lookup(const std::map<TKM, TV>& map, const TK& key, TV& value)
{
  auto it = map.find(key);
  if (it != map.end())
  {
    value = it->second;
    return true;
  }
  else
  {
    return false;
  }
}

只要TK可以隐式转换为TKM,就可以在键类型为TKM的映射中使用以TK类型作为键调用Lookup方法。

3

你可以为 key 引入另一个模板参数,如 @Ton van den Heuvel answered 所述,另一种方法是将其从 模板参数推导 中排除:

template <typename TK, typename TV>
bool Lookup(std::map<TK, TV>& map, const std::type_identity_t<TK>& key, TV& value)

然后 TK 只会从第一个参数 map 中推断; 如果您将 const char[] 作为 key 传递给函数,它将被转换为 std::string 然后作为参数传递。您可以使其成为传递常量引用以避免潜在的不必要复制。

LIVE

顺便说一下: std::type_identity 在 C++20 中得到支持; 如果您的编译器不支持它,您可以轻松地自己创建。

template<typename T> struct type_identity { typedef T type; };

在这种情况下,key的值将是什么? std::type_identity_t<TK>不是一个空结构体吗? - Ton van den Heuvel
@TonvandenHeuvel std::type_identity 只有一个成员类型,实际上,std::type_identity_t<TK>TK 是相同的,它们都是引用类型 TK,除了使模板参数不可推断外,然后 TK 将仅从第一个参数推导,避免了类型推断中的歧义。 - songyuanyao
啊,我现在明白了,这是一个有趣的解决方案。 - Ton van den Heuvel
无法使用C++17编译。 - Jabberwocky

1
你需要两个东西。首先,可以单独推断键类型(下面是typename K)。其次,您希望将键作为const限定引用传递,并使用C++14透明比较函数(下面是typename Comp)设置映射,以避免不必要的复制(有关透明比较器的详细信息,请参见this thread)。
template <typename TK, typename TV, typename Comp, typename K>
bool Lookup(const std::map<TK, TV, Comp>& map,  const K& key, TV& value)
{
   // Same as before...
}

std::map<std::string, std::string, std::less<>> m;

指定std::less<>作为std::map比较类型,确保std::map::find的第3和第4重载可用。
请注意,我还额外添加了const修饰符到地图参数本身,因为Lookup模板不会修改它。

换句话说,当键是 std::string 时,我需要声明 std::map<std::string, some_type, std::less<>> m; 而不是 std::map<std::string, some_type> m; 吗? - Jabberwocky
不幸的是,是的。 - lubgr
@Jabberwocky,我不会这么说,只有在你不想为从非std::string键到std::string的隐式转换付费时,在执行查找时才应该使用透明比较器。 - Ton van den Heuvel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接