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)?
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.
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; };
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.
Related
I want a get_value template function.
please see the following code:
template<typename T>
(something i want) get_value(const T& m, int key) {
auto it = m.upper_bound(key);
return it == m.begin() ? (*it).second : (*--it).second; // please notice here,
// if my instance is map<int, map<string, int>> the return type should be m.second's type
// that's map<string, int>
}
int main() {
std::map<int, std::map<std::string, int>> m;
auto& it = get_value(m, 10);
}
as you can see, the template function should have a return type, which is depend on instance's second type, is there any method i can get this type to make the code runnable?
The "second type" in a std::map<K,V> is called std::map<K,V>::mapped_type. However, you can use auto to let the compiler deduce that for you:
template<typename T>
auto get_value(const T& m, int key) {
auto it = m.upper_bound(key);
return it == m.begin() ? (*it).second : (*--it).second; // please notice here,
}
or with explicit type:
template<typename T>
typename T::mapped_type get_value(const T& m, int key) {
auto it = m.upper_bound(key);
return it == m.begin() ? (*it).second : (*--it).second; // please notice here,
}
If you can use C++14 standard or above, the safest way to go would be to use decltype(auto) as the return type:
template<typename T>
decltype(auto) get_value(const T& m, int key);
The difference to a plain auto is that decltype(auto) preserves cv-qualifiers, and in your case you most likely want to forward exactly the same type that std::map gives you.
For example, since the actual code uses std::map<int, std::map<std::string, int>>, you might want to avoid copying the return value every time, and decltype(auto) will achieve that.
Update 1: Replaced code with code that builds standalone to make it clearer
Update 2: Partially fixed instantiation problem based on #Jarod42 comment but still failing
I have some code that wants to search a dictionary with a string key in a case insensitive fashion. The full code is below.
The code as shown compiles, links and works without any warnings, errors or problems.
If I uncomment the line that explictly instantiates the template then I get
warning C4667: 'const std::_Tree_const_iterator>>> findKeyIC(const MyMap &,const std::wstring &)': no function template defined that matches forced instantiation
Also, in the line of FindNameMyMapIC that calls findKeyIC intellisense complains:
E0304 no instance of function template "findKeyIC" matches the argument list argument types are: (const MyMap, const std::wstring)
I'm most interested in the Intellisense problem but felt the explicit instantiation problem could be related.
#include <boost/algorithm/string.hpp>
#include <map>
typedef std::map<std::wstring, int> MyMap;
const MyMap gMyMap = { { L"A", 0}, { L"B", 1 }, { L"C", 2 } };
// Cases insensitive comparison of two strings, return true if they match.
// Supports std:string variants and char*/wchar*.
template<class StrType>
inline bool StrIEquals(const StrType& str1, const StrType& str2)
{
return boost::iequals(str1, str2);
}
inline bool StrIEquals(const char* const& str1, const char* const& str2)
{
return (_stricmp(str1, str2) == 0);
}
inline bool StrIEquals(const wchar_t* const& str1, const wchar_t* const& str2)
{
return (_wcsicmp(str1, str2) == 0);
}
// Returns an iterator that refers to the location of an element in a map that has a key equivalent
// to a specified key using case insensitive comparison.
template <typename Key, typename Value, typename Reference, template<typename ...> class Container>
inline auto findKeyIC(const Container<Key, Value>& container, Reference const& key)
{
auto it = container.cbegin();
for (; it != container.cend(); ++it)
{
if (StrIEquals((const Key)it->first, (const Key)key))
return it;
}
return it;
}
// template const MyMap::const_iterator findKeyIC(const MyMap& container, const std::wstring& key);
int FindNameMyMapIC(const std::wstring& name)
{
auto it = findKeyIC(gMyMap, name);
if (it != gMyMap.cend())
{
return it->second;
}
return -1;
}
I have this code to explicitly instantiate the template:
MyMap::const_iterator findKeyIC(const MyMap& container, const std::wstring& key);
No, you declare non template function overload.
I think that intellisense is disturbed by "variadic" container whereas std::map is not.
If possible, I would change your map comparer to compare case insensitive, so you might use map::find (logarithm look-up instead of linear search)
Else, you might do:
// Returns an iterator that refers to the location of an element in a map that has a key equivalent
// to a specified key using case insensitive comparison.
template <typename Key, typename Container>
auto findKeyIC(const Container& container, Key const& key)
{
return std::find_if(container.cbegin(), container.cend(),
[&](const auto& p){ return StrIEquals(p.first, key); });
}
One can fix the Intellisense error by replacing the auto in the template definition with the explicit type and by explicitly listing the std:map template optional parameters Compare and Allocator.
Thus that part of the code becomes:
// Returns an iterator that refers to the location of an element in a map that has a key equivalent
// to a specified key using case insensitive comparison.
template <typename Key, typename Value, typename Reference, class Compare, class Allocator, template<typename ...> class Container>
inline typename Container<Key, Value, Compare, Allocator>::const_iterator findKeyIC(const Container<Key, Value, Compare, Allocator>& container, Reference const& key)
{
auto it = container.cbegin();
for (; it != container.cend(); ++it)
{
if (StrIEquals((const Key)it->first, (const Key)key))
return it;
}
return it;
}
template typename MyMap::const_iterator findKeyIC(const MyMap& container, std::wstring const& key);
This code compiles and links with no compiler warnings or errors and no intellisense errors.
However it leaves an intellisense warning on the findKeyIC in the explicit template instantiation line.
Function definition for 'findKeyIC' not found.
I found this implementation of a few common features of functional programming, e.g. map / reduce:
(I'm aware stuff like that is aparently coming or partially present in new C++ versions)
github link
A part of the code:
template <typename T, typename U>
U foldLeft(const std::vector<T>& data,
const U& initialValue,
const std::function<U(U,T)>& foldFn) {
typedef typename std::vector<T>::const_iterator Iterator;
U accumulator = initialValue;
Iterator end = data.cend();
for (Iterator it = data.cbegin(); it != end; ++it) {
accumulator = foldFn(accumulator, *it);
}
return accumulator;
}
template <typename T, typename U>
std::vector<U> map(const std::vector<T>& data, const std::function<U(T)> mapper) {
std::vector<U> result;
foldLeft<T, std::vector<U>&>(data, result, [mapper] (std::vector<U>& res, T value) -> std::vector<U>& {
res.push_back(mapper(value));
return res;
});
return result;
}
Usage example:
std::vector<int> biggerInts = map<int,int>(test, [] (int num) { return num + 10; });
The type arguments T,U have to be fully qualified for this to compile, as shown in the example, with e.g. map< int,int >( ... ).
This implementation is for C++11, as mentioned on the linked-to page.
Is it possible with newer C++ versions (or even 11) now to make the use of this less verbose, i.e. making the types U,T deduce automatically?
I have googled for that and only found that there is apparently some improvement for class template, as opposed to function template, argument deduction in C++17.
But since I only ever used templates in a rather basic manner, I was wondering whether there is something in existence that I'm not aware of which could improve this implementation verboseness-wise.
You can rewrite map signature to be:
template <typename T, typename M, typename U = decltype(std::declval<M>()(T{}))>
std::vector<U> map(const std::vector<T>& data, const M mapper)
then T will be deduced as value_type of vector's items.
M is any callable object.
U is deduced as return type of M() functor when called for T{}.
Below
std::vector<int> biggerInts = map(test, [] (int num) { return num + 10; });
^^^^ empty template arguments list
works fine.
Live demo
More general templates make template argument deduction easier.
One principle: it is often a mistake to use a std::function as a templated function's parameter. std::function is a type erasure, for use when something needs to store some unknown invokable thing as a specific type. But templates already have the ability to handle any arbitrary invokable type. So if we just use a generic typename FuncT template parameter, it can be deduced for a raw pointer-to-function, a lambda, or another class with operator() directly.
We might as well also get more general and accept any input container instead of just vector, then determine T from it, if it's even directly needed.
So for C++11 I would rewrite these:
// C++20 is adding std::remove_cvref, but it's trivial to implement:
template <typename T>
using remove_cvref_t =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <typename Container, typename U, typename FuncT>
remove_cvref_t<U> foldLeft(
const Container& data,
U&& initialValue,
const FuncT& foldFn) {
remove_cvref_t<U> accumulator = std::forward<U>(initialValue);
for (const auto& elem : data) {
accumulator = foldFn(std::move(accumulator), elem);
}
return accumulator;
}
template <typename Container, typename FuncT>
auto map(const Container& data, const FuncT& mapper)
-> std::vector<remove_cvref_t<decltype(mapper(*std::begin(data)))>>
{
using T = remove_cvref_t<decltype(*std::begin(data))>;
using ResultT = std::vector<remove_cvref_t<decltype(mapper(std::declval<const T&>()))>>;
ResultT result;
foldLeft(data, std::ref(result), [&mapper] (ResultT &res, const T& value) -> ResultT& {
res.push_back(mapper(value));
return res;
});
return result;
}
See the working program on coliru.
There was one unfortunate thing about the old map: it potentially copied the result vector at every iteration. The = in accumulator = foldFn(accumulator, *it); is a self-assignment, which might do nothing, or might allocate new memory, copy contents, then free the old memory and update the container. So instead I've changed the U for foldLeft in this case to a std::reference_wrapper. The = in that case will still "rebind" the wrapper to the same object, but that will at least be quick.
In C++14 and later, you could do away with finding T within map by using a generic lambda: [&mapper] (std::vector<U>& res, const auto& value) ...
I want to save some repeating work and write a function that mimicks Java
.containsKey() method.
Basically I would like to have something like this:
using namespace std;
map<string,XYclass> mymap;
if (!contains(mymap,"key as string") ) cout << "key not found" << endl;
In C++ one can check, if a map contains key in following way:
m.find(str) != m.end();
I want to write a generic method that returns true if a key is contained in a map.
So far I have following:
template<typename A, typename B> inline bool contains(const std::map< A, B > m, const A& str)
{
return m.find(str) != m.end();
}
which will fail to do template argument deduction, when I run it on a map<string,int> with following call contains(mymap,"key as string"), as "key as string" is actually a char array.
Function works fine when I do explicit instantiation (i.e. by using following call contains<string,int>(mymap,"key as string"))
How to do it properly?
One can exclude parameters from template argument deduction with the below identity trick:
template <typename T>
struct identity { typedef T type; };
template <typename A, typename B>
inline bool contains(const std::map<A, B>& m
, const typename identity<A>::type& str)
{
return m.find(str) != m.end();
}
DEMO
You don't need to specify type template arguments explicitly now.
To be precise, std::map has the total of four type template parameters:
template <typename A, typename B, typename Cmp, typename Alloc>
inline bool contains(const std::map<A, B, Cmp, Alloc>& m
, const typename identity<A>::type& str);
Don't hard-code it to std::map. The expression c.find( k ) != c.end() will work for any container with a find method returning an iterator. The function is applicable to any such types.
As others have noted, std::map has additional template parameters for the comparison function and the node allocator. In principle, listing all its parameters violates the separation of concerns.
template< typename container, typename key >
auto contains( container const & c, key const & k )
-> decltype( c.find( k ) != c.end() )
{ return c.find( k ) != c.end(); }
The decltype specifier performs SFINAE, in case you want other overloads.
I would go for declaring contains() function as template with 3 arguments:
template<typename Key, typename Value, typename Arg>
inline bool map_contains(const std::map< Key, Value > m, const Arg& value)
{
return m.find(value) != m.end();
}
Note, that now Arg must be implicitly convertible to Key. You can easily remove this requirement - all you need to do is to call find() with value explicitly casted to Key type.
Live demo: click.
Is there a way to specify the default value std::map's operator[] returns when an key does not exist?
While this does not exactly answer the question, I have circumvented the problem with code like this:
struct IntDefaultedToMinusOne
{
int i = -1;
};
std::map<std::string, IntDefaultedToMinusOne > mymap;
No, there isn't. The simplest solution is to write your own free template function to do this. Something like:
#include <string>
#include <map>
using namespace std;
template <typename K, typename V>
V GetWithDef(const std::map <K,V> & m, const K & key, const V & defval ) {
typename std::map<K,V>::const_iterator it = m.find( key );
if ( it == m.end() ) {
return defval;
}
else {
return it->second;
}
}
int main() {
map <string,int> x;
...
int i = GetWithDef( x, string("foo"), 42 );
}
C++11 Update
Purpose: Account for generic associative containers, as well as optional comparator and allocator parameters.
template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
typename C<K,V,Args...>::const_iterator it = m.find( key );
if (it == m.end())
return defval;
return it->second;
}
C++17 provides try_emplace which does exactly this. It takes a key and an argument list for the value constructor and returns a pair: an iterator and a bool.: http://en.cppreference.com/w/cpp/container/map/try_emplace
The C++ standard (23.3.1.2) specifies that the newly inserted value is default constructed, so map itself doesn't provide a way of doing it. Your choices are:
Give the value type a default constructor that initialises it to the value you want, or
Wrap the map in your own class that provides a default value and implements operator[] to insert that default.
The value is initialized using the default constructor, as the other answers say. However, it is useful to add that in case of simple types (integral types such as int, float, pointer or POD (plan old data) types), the values are zero-initialized (or zeroed by value-initialization (which is effectively the same thing), depending on which version of C++ is used).
Anyway, the bottomline is, that maps with simple types will zero-initialize the new items automatically. So in some cases, there is no need to worry about explicitly specifying the default initial value.
std::map<int, char*> map;
typedef char *P;
char *p = map[123],
*p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0
See Do the parentheses after the type name make a difference with new? for more details on the matter.
There is no way to specify the default value - it is always value constructed by the default (zero parameter constructor).
In fact operator[] probably does more than you expect as if a value does not exist for the given key in the map it will insert a new one with the value from the default constructor.
template<typename T, T X>
struct Default {
Default () : val(T(X)) {}
Default (T const & val) : val(val) {}
operator T & () { return val; }
operator T const & () const { return val; }
T val;
};
<...>
std::map<KeyType, Default<ValueType, DefaultValue> > mapping;
More General Version, Support C++98/03 and More Containers
Works with generic associative containers, the only template parameter is the container type itself.
Supported containers: std::map, std::multimap, std::unordered_map, std::unordered_multimap, wxHashMap, QMap, QMultiMap, QHash, QMultiHash, etc.
template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m,
const typename MAP::key_type& key,
const typename MAP::mapped_type& defval)
{
typename MAP::const_iterator it = m.find(key);
if (it == m.end())
return defval;
return it->second;
}
Usage:
std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");
Here is a similar implementation by using a wrapper class, which is more similar to the method get() of dict type in Python: https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp
template<typename MAP>
struct map_wrapper
{
typedef typename MAP::key_type K;
typedef typename MAP::mapped_type V;
typedef typename MAP::const_iterator CIT;
map_wrapper(const MAP& m) :m_map(m) {}
const V& get(const K& key, const V& default_val) const
{
CIT it = m_map.find(key);
if (it == m_map.end())
return default_val;
return it->second;
}
private:
const MAP& m_map;
};
template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
return map_wrapper<MAP>(m);
}
Usage:
std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
One workaround is to use map::at() instead of [].
If a key does not exist, at throws an exception.
Even nicer, this also works for vectors, and is thus suited for generic programming where you may swap the map with a vector.
Using a custom value for unregistered key may be dangerous since that custom value (like -1) may be processed further down in the code. With exceptions, it's easier to spot bugs.
Expanding on the answer https://stackoverflow.com/a/2333816/272642, this template function uses std::map's key_type and mapped_type typedefs to deduce the type of key and def.
This doesn't work with containers without these typedefs.
template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
typename C::const_iterator it = m.find(key);
if (it == m.end())
return def;
return it->second;
}
This allows you to use
std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);
without needing to cast the arguments like std::string("a"), (int*) NULL.
Pre-C++17, use std::map::insert(), for newer versions use try_emplace(). It may be counter-intuitive, but these functions effectively have the behaviour of operator[] with custom default values.
Realizing that I'm quite late to this party, but if you're interested in the behaviour of operator[] with custom defaults (that is: find the element with the given key, if it isn't present insert a chosen default value and return a reference to either the newly inserted value or the existing value), there is already a function available to you pre C++17: std::map::insert(). insert will not actually insert if the key already exists, but instead return an iterator to the existing value.
Say, you wanted a map of string-to-int and insert a default value of 42 if the key wasn't present yet:
std::map<std::string, int> answers;
int count_answers( const std::string &question)
{
auto &value = answers.insert( {question, 42}).first->second;
return value++;
}
int main() {
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
return 0;
}
which should output 42, 43 and 44.
If the cost of constructing the map value is high (if either copying/moving the key or the value type is expensive), this comes at a significant performance penalty, which would be circumvented with C++17's try_emplace().
If you have access to C++17, my solution is as follows:
std::map<std::string, std::optional<int>> myNullables;
std::cout << myNullables["empty-key"].value_or(-1) << std::endl;
This allows you to specify a 'default value' at each use of the map. This may not necessarily be what you want or need, but I'll post it here for the sake of completeness. This solution lends itself well to a functional paradigm, as maps (and dictionaries) are often used with such a style anyway:
Map<String, int> myNullables;
print(myNullables["empty-key"] ?? -1);
Maybe you can give a custom allocator who allocate with a default value you want.
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
With C++20 it is simple to write such getter:
constexpr auto &getOrDefault(const auto &map, const auto &key, const auto &defaultValue)
{
const auto itr = map.find(key);
return itr == map.cend() ? defaultValue : itr->second;
}
Here is a correct approach that will conditionally return a reference if the caller passes in an lvalue reference to the mapped type.
template <typename Map, typename DefVal>
using get_default_return_t = std::conditional_t<std::is_same_v<std::decay_t<DefVal>,
typename Map::mapped_type> && std::is_lvalue_reference_v<DefVal>,
const typename Map::mapped_type&, typename Map::mapped_type>;
template <typename Map, typename Key, typename DefVal>
get_default_return_t<Map, DefVal> get_default(const Map& map, const Key& key, DefVal&& defval)
{
auto i = map.find(key);
return i != map.end() ? i->second : defval;
}
int main()
{
std::map<std::string, std::string> map;
const char cstr[] = "world";
std::string str = "world";
auto& ref = get_default(map, "hello", str);
auto& ref2 = get_default(map, "hello", std::string{"world"}); // fails to compile
auto& ref3 = get_default(map, "hello", cstr); // fails to compile
return 0;
}
If you would like to keep using operator[] just like when you don't have to specify a default value other than what comes out from T() (where T is the value type), you can inherit T and specify a different default value in the constructor:
#include <iostream>
#include <map>
#include <string>
int main() {
class string_with_my_default : public std::string {
public:
string_with_my_default() : std::string("my default") {}
};
std::map<std::string, string_with_my_default> m;
std::cout << m["first-key"] << std::endl;
}
However, if T is a primitive type, try this:
#include <iostream>
#include <map>
#include <string>
template <int default_val>
class int_with_my_default {
private:
int val = default_val;
public:
operator int &() { return val; }
int* operator &() { return &val; }
};
int main() {
std::map<std::string, int_with_my_default<1> > m;
std::cout << m["first-key"] << std::endl;
++ m["second-key"];
std::cout << m["second-key"] << std::endl;
}
See also C++ Class wrapper around fundamental types