Jabberwocky
Jabberwocky

Reputation: 50774

Function template and ambigous template parameter

I have several std::map<some_type, some_other_type> and I'm trying to write a function template Lookup as shown below.

The function template works fine when the key is a pointer or a scalar, but if the key is std::string there are problems.

#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";
}

I understand why Lookup(m, "2", x) doesn't compile because the type of "2" is not std::string but is there a way to write the function template so I can use Lookup(m, "2", x) as well as Lookup(m, key, x), key being a std::string?

And if yes this raises a second question:

bool Lookup(std::map<TK, TV>& map,  TK key, TV& value)

key is passed by value and if the type of key is std::string, a copy is made. Is there a way to pass key by reference (or some C++14 and plus magic) and still being able to use Lookup(m, "2", x)?

Upvotes: 3

Views: 96

Answers (3)

songyuanyao
songyuanyao

Reputation: 172924

You can introduce another template parameter for key as @Ton van den Heuvel answered, another way is to exclude it from template argument deduction:

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

Then TK will be only deduced from the 1st parameter map; if you pass a const char[] to the function as key it'll be converted to std::string then passed as the argument. And you can make is pass-by-const-reference to avoid potential unnecessary copy.

LIVE

BTW: std::type_identity is supported from C++20; if your compiler doesn't support it, you can make your own easily.

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

Upvotes: 3

Ton van den Heuvel
Ton van den Heuvel

Reputation: 10528

One way to solve this is to introduce a separate type parameter for the key type, as follows:

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;
  }
}

As long as TK is implicitly convertible to TKM, Lookup can be called with a key of type TK in conjunction with a map that has key type TKM.

Upvotes: 3

lubgr
lubgr

Reputation: 38277

You need two things here. First, the key type can be deduced seaparately (typename K below). Second, you want to pass the key as const-qualified reference and setup the map with a C++14 transparent comparison function (typename Comp below) to avoid unnecessary copies (see this thread for details about transparent comparators).

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;

Specifying std::less<> as the std::map comparison type makes sure that overloads #3 and #4 of std::map::find are available.

Note that I have additionally const-qualified the map parameter itself, as the Lookup template doesn't modify it.

Upvotes: 1

Related Questions