std::map 默认值

发布于 2024-08-22 17:49:13 字数 75 浏览 4 评论 0原文

有没有办法指定键不存在时 std::mapoperator[] 返回的默认值?

Is there a way to specify the default value std::map's operator[] returns when an key does not exist?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

老旧海报 2024-08-29 17:49:14

无法指定默认值 - 它始终是由默认值(零参数构造函数)构造的值。

事实上,operator[] 的作用可能比您预期的要多,就好像映射中给定键不存在值一样,它将使用默认构造函数中的值插入一个新值。

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.

我ぃ本無心為│何有愛 2024-08-29 17:49:14
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;
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;
梦幻的味道 2024-08-29 17:49:14

更通用的版本,支持C++98/03和更多容器

适用于通用关联容器,唯一的模板参数是容器类型本身。

支持的容器:std::mapstd::multimapstd::unordered_mapstd::unordered_multimap 、wxHashMap、QMap、QMultiMap、QHash、QMultiHash 等。

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

用法:

std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");

这里有一个类似的使用包装类的实现,它更类似于Python中dict类型的方法get()https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp< /a>

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

用法:

std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");

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");
十年不长 2024-08-29 17:49:14

C++17 之前的版本,使用 std::map::insert(),较新版本使用 try_emplace()。这可能违反直觉,但这些函数实际上具有带有自定义默认值的 operator[] 行为。

意识到我参加这个聚会已经很晚了,但是如果您对具有自定义默认值的 operator[] 的行为感兴趣(即:查找具有给定键的元素,如果它不是)如果当前插入一个选定的默认值并返回对新插入的值或现有值的引用),在 C++17 之前已经有一个函数可供您使用:std::地图::插入()。如果键已经存在,则 insert 不会实际插入,而是返回一个指向现有值的迭代器。

假设您想要一个字符串到整数的映射,并在键不存在时插入默认值 42:

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

这应该输出 42、43 和 44。

如果构建映射值的成本很高(如果复制/移动键或值类型的成本很高),这会带来显着的性能损失,这可以通过 C++17 的 try_emplace() 来规避。

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().

彼岸花似海 2024-08-29 17:49:14

一种解决方法是使用 map::at() 而不是 []
如果键不存在,at 会抛出异常。
更好的是,这也适用于向量,因此适合通用编程,您可以将映射与向量交换。

对未注册的密钥使用自定义值可能很危险,因为该自定义值(如 -1)可能会在代码中进一步处理。除了例外情况,更容易发现错误。

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.

鹿港小镇 2024-08-29 17:49:14

扩展答案 https://stackoverflow.com/a/2333816/272642,此模板函数使用 std ::mapkey_typemapped_type typedef 来推断 keydef 的类型。
这不适用于没有这些 typedef 的容器。

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

即可使用,例如 std::string("a"), (int*) NULL

std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);

这允许您无需强制转换参数

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.

我家小可爱 2024-08-29 17:49:14

如果您可以访问 C++17,我的解决方案如下:

std::map<std::string, std::optional<int>> myNullables;
std::cout << myNullables["empty-key"].value_or(-1) << std::endl;

这允许您在每次使用映射时指定“默认值”。这可能不一定是您想要或需要的,但为了完整起见,我将其发布在这里。该解决方案非常适合函数式范例,因为地图(和字典)通常以这种方式使用:

Map<String, int> myNullables;
print(myNullables["empty-key"] ?? -1);

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);
等待我真够勒 2024-08-29 17:49:14

使用C++20,编写这样的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;
}

@425nespКоеКто 注意到,仅当 getOrDefault 作为常量表达式调用时,编译器才不会编译具有未定义行为的代码。
如果将 getOrDefault 作为常规函数调用,则最终可能会得到对已销毁临时文件的引用。

例子

constexpr auto &v = getOrDefault(m, key, "foo"); // Compile error!
auto &v = getOrDefault(map, key, "foo"); // Undefined behavior!

const auto value = "foo";
auto &v = getOrDefault(map, key, value); // Ok

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

As @425nesp and КоеКто noticed, the compiler won't compile code with undefined behavior only if getOrDefault called as constant expression.
If getOrDefault called as regular function, it possible to end up with a reference to the destroyed temporary.

Example

constexpr auto &v = getOrDefault(m, key, "foo"); // Compile error!
auto &v = getOrDefault(map, key, "foo"); // Undefined behavior!

const auto value = "foo";
auto &v = getOrDefault(map, key, value); // Ok
禾厶谷欠 2024-08-29 17:49:14

也许您可以提供一个自定义分配器,它可以分配您想要的默认值。

template < class Key, class T, class Compare = less<Key>,
       class Allocator = allocator<pair<const Key,T> > > class map;

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;
背叛残局 2024-08-29 17:49:14

这是一种正确的方法,如果调用者传入映射类型的左值引用,它将有条件地返回引用。

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

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;
}
↘人皮目录ツ 2024-08-29 17:49:14

如果您想继续使用 operator[],就像您不必指定除 T() 中的默认值(其中 >T 是值类型),您可以继承 T 并在构造函数中指定不同的默认值:

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

但是,如果 T 是原始类型,请尝试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;
}

另请参阅围绕基本类型的 C++ 类包装器

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

献世佛 2024-08-29 17:49:13

虽然这并不能完全回答问题,但我已经用这样的代码规避了这个问题:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

std::map<std::string, IntDefaultedToMinusOne > mymap;

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;
箹锭⒈辈孓 2024-08-29 17:49:13

不,没有。最简单的解决方案是编写您自己的免费模板函数来执行此操作。类似于:

#include <string>
#include <map>
using namespace std;

template <typename K, typename V>
const 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 更新

目的:考虑通用关联容器,以及可选的比较器和分配器参数。

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

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>
const 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;
}
安静被遗忘 2024-08-29 17:49:13

C++17 提供的 try_emplace 正是这样做的。它需要一个键和一个值构造函数的参数列表,并返回一对:一个迭代器和一个布尔值。:http://en.cppreference.com/w/cpp/container/map/try_emplace

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

非要怀念 2024-08-29 17:49:13

C++ 标准 (23.3.1.2) 指定新插入的值是默认构造的,因此 map 本身不提供执行此操作的方法。您的选择是:

  • 为值类型提供一个默认构造函数,将其初始化为您想要的值,或者
  • 将映射包装在您自己的类中,该类提供默认值并实现 operator[] 来插入该默认值。

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.
_失温 2024-08-29 17:49:13

正如其他答案所说,该值是使用默认构造函数初始化的。然而,补充一点是有用的,在简单类型(整数类型,如 int、float、指针或 POD(计划旧数据)类型)的情况下,值被零初始化(或通过值初始化归零(这实际上是相同的事情),具体取决于使用的 C++ 版本)。

无论如何,底线是,具有简单类型的映射将自动对新项目进行零初始化。所以在某些情况下,无需担心显式指定默认初始值。

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

请参阅 添加括号在类型名称之后 make a Difference 与 new? 有关此事的更多详细信息。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文