Existe uma maneira de especificar o valor padrão std::map
's operator[]
retorna quando uma chave não existe?
Respostas:
Não, não existe. A solução mais simples é escrever sua própria função de modelo grátis para fazer isso. Algo como:
#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 );
}
Atualização C ++ 11
Objetivo: contabilizar contêineres associativos genéricos, bem como parâmetros opcionais de comparador e alocador.
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;
}
operator[]
com o valor padrão, o valor padrão deve ser inserido no mapa dentro do if ( it == m.end() )
bloco
Embora isso não responda exatamente à pergunta, contornei o problema com um código como este:
struct IntDefaultedToMinusOne
{
int i = -1;
};
std::map<std::string, IntDefaultedToMinusOne > mymap;
O padrão C ++ (23.3.1.2) especifica que o valor recém-inserido é construído por padrão, portanto map
, ele próprio não fornece uma maneira de fazê-lo. Suas escolhas são:
operator[]
para inserir esse padrão.O C ++ 17 fornece try_emplace
exatamente isso. Leva uma chave e uma lista de argumentos para o construtor de valor e retorna um par: um iterator
e um bool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
Mais versão geral, suporte C ++ 98/03 e mais contêineres
Funciona com containers associativos genéricos, o único parâmetro de template é o próprio tipo de container.
Recipientes suportados: 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;
}
Uso:
std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");
Aqui está uma implementação semelhante usando uma classe wrapper, que é mais semelhante ao método get()
do dict
tipo em 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);
}
Uso:
std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
Não há como especificar o valor padrão - é sempre o valor construído pelo padrão (construtor de parâmetro zero).
Na verdade, operator[]
provavelmente faz mais do que você espera, pois se um valor não existir para a chave fornecida no mapa, ele irá inserir um novo com o valor do construtor padrão.
find
que retorna o iterador final se nenhum elemento existir para uma determinada chave.
find
nesse caso?
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;
O valor é inicializado usando o construtor padrão, como dizem as outras respostas. No entanto, é útil adicionar que no caso de tipos simples (tipos integrais como int, float, ponteiro ou POD (dados antigos de plano)), os valores são inicializados por zero (ou zerados por inicialização de valor (que é efetivamente a mesma coisa), dependendo de qual versão do C ++ é usada).
De qualquer forma, o ponto principal é que os mapas com tipos simples inicializarão com zeros os novos itens automaticamente. Portanto, em alguns casos, não há necessidade de se preocupar em especificar explicitamente o valor inicial padrão.
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
Consulte Os parênteses após o nome do tipo fazem diferença com o novo? para mais detalhes sobre o assunto.
Uma solução alternativa é usar em map::at()
vez de []
. Se uma chave não existe,at
lança uma exceção. Ainda melhor, isso também funciona para vetores e, portanto, é adequado para programação genérica, onde você pode trocar o mapa por um vetor.
Usar um valor personalizado para uma chave não registrada pode ser perigoso, pois esse valor personalizado (como -1) pode ser processado posteriormente no código. Com exceções, é mais fácil detectar bugs.
Talvez você possa fornecer a um alocador personalizado que aloque com um valor padrão que você deseja.
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
operator[]
retorna um objeto criado por invocação T()
, não importa o que o alocador faça.
construct
método dos alocadores ? Seria possível mudar isso, eu acho. No entanto, suspeito de uma construct
função que não faz outra coisa senão new(p) T(t);
não é bem formada. EDIT: Em retrospectiva, isso foi uma tolice, caso contrário, todos os valores seriam os mesmos: P Onde está meu café ...
operator[]
retorna (*((insert(make_pair(x, T()))).first)).second
. Portanto, a menos que esteja faltando alguma coisa, essa resposta está errada.
insert
com um T()
, mas dentro de inserir é quando ele usará o alocador, obterá memória para um novo e T
então chamará construct
essa memória com o parâmetro que foi fornecido, que é T()
. Portanto, é realmente possível alterar o comportamento de fazer operator[]
com que ele retorne algo diferente, mas o alocador não consegue diferenciar por que está sendo chamado. Portanto, mesmo que construct
ignorássemos seu parâmetro e usássemos nosso valor especial, isso significaria que cada elemento construído tinha esse valor, o que é ruim.
Expandindo a resposta https://stackoverflow.com/a/2333816/272642 , este modelo usa a função std::map
s' key_type
e mapped_type
typedefs para deduzir o tipo de key
e def
. Isso não funciona com contêineres sem esses 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;
}
Isso permite que você use
std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);
sem precisar lançar os argumentos como std::string("a"), (int*) NULL
.
Usar std::map::insert()
.
Percebi que estou bem atrasado para esta festa, mas se você estiver interessado no comportamento de operator[]
padrões personalizados (ou seja, encontre o elemento com a chave fornecida, se não estiver presente, insira um elemento no mapa com um valor padrão escolhida e retornar uma referência, quer ao valor recém-inseridos ou o valor existente), já existe uma função disponível para você pré C ++ 17: std::map::insert()
. insert
não irá realmente inserir se a chave já existir, mas em vez disso, retornará um iterador para o valor existente.
Digamos que você queira um mapa de string-to-int e insere um valor padrão de 42 se a chave ainda não estiver presente:
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;
}
que deve produzir 42, 43 e 44.
Se o custo de construir o valor do mapa for alto (se copiar / mover a chave ou o tipo de valor for caro), isso terá uma penalidade de desempenho significativa, que acho que seria contornada com o C ++ 17 try_emplace
.