Std::Map Default Value for Build-In Type

std::map default value for build-in type

This is defined in the standard, yes. map is performing "default initialization" in this case. As you say, for class types, that calls a no-arguments constructor.

For built-in types, in the '98 standard, see section 8.5, "Initializers":

To default-initialize an object of type T means:

  • if T is a non-POD ...
  • if T is an array type ...
  • otherwise, the storage for the object is zero-initialized

And, previously,

To zero-initialize storage for an object of type T means:

  • if T is a scalar type, the storage is set to the value 0 (zero) converted to T

Scalar types are:

  • Arithmetic types (integer, floating point)
  • Enumeration types
  • Pointer types
  • Pointer to member types

In particular, the behaviour you see with an integer (initialized to zero) is defined by the standard, and you can rely on it.

mapint,int default values

As soon as you access the map with the [] operator, if the key doesn't exist it gets added. The int gets "value initialization" invoked - so it will get a value of 0.

c++03: default constructor for build-in types in std::map

Standard containers (map, vector, etc...) will always value-initialize their elements.

Roughly speaking, value-initialization is:

  • default-initialization if there is a default constructor
  • zero-initialization otherwise

(Some would say, the best of both worlds)

The syntax is simple: T t = T(); will value-initialize t (and T t{}; in C++11).

When you use map<K,V>::operator[], the "value" part of the pair is value-initialized, which for a built-in type yields 0.

std::map default value

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

std::mapstring,int default initialization of value

Yes, this code would work for any type of the key, including double. The reason this works is that the non-const operator [] returns a reference to the value at the key, not a copy of that value. It is that reference to which the ++ operator gets applied.

The code fragment that you show works as follows:

  • For each key t of type string in the str container,
  • The map w is searched for the given key
  • Since the entry is not there, a new one gets inserted into the map
  • Since the key of the entry is known, but the value is not, a default (value-initialized, e.i. 0 for int) object for the value gets created
  • A reference to the newly created object (in this case, int& initialized to zero) is returned to the caller
  • The ++ operator is applied to the reference returned from the [], which changes 0 to 1 (or 0.0 to 1.0, etc.)

std::map default value (move only types)

This can be achieved with a proxy object.

template <typename T>
class PossiblyOwner
{
public:
struct Reference {};

PossiblyOwner(const PossiblyOwner & other)
: m_own(other.m_own),
m_ref(m_own.has_value() ? m_own.value() : other.m_ref)
{}
PossiblyOwner(PossiblyOwner && other)
: m_own(std::move(other.m_own)),
m_ref(m_own.has_value() ? m_own.value() : other.m_ref)
{}

PossiblyOwner(T && val) : m_own(std::move(val)), m_ref(m_own.value()) {}
PossiblyOwner(const T & val) : m_own(val), m_ref(m_own.value()) {}
PossiblyOwner(Reference, const T & val) : m_ref(val) {}
const T& value () const { return m_ref; }
operator const T& () const { return m_ref; }

// convenience operators, possibly also define ->, +, etc.
// but they are not strictly needed
auto operator *() const { return *m_ref; }
private:
std::optional<T> m_own;
const T & m_ref;
};

// Not strictly required
template <typename T>
std::ostream & operator<<(std::ostream & out,
const PossiblyOwner<T> & value)
{
return out << value.value();
}
template <typename Container, typename Key, typename ...DefaultArgs>
auto GetOrDefault(const Container & container, const Key & key,
DefaultArgs ...defaultArgs)
-> PossiblyOwner<decltype(container.find(key)->second)>
{
auto it = container.find(key);
using value_type = decltype(it->second);
using ret_type = PossiblyOwner<value_type>;
if (it == container.end())
return {value_type(std::forward<DefaultArgs>(defaultArgs)...)};
else
return {typename ret_type::Reference{}, it->second};
}

Then the usage can be:

int main()
{
std::map<int, std::unique_ptr<std::string>> mapping;
mapping.emplace(1, std::make_unique<std::string>("one"));
mapping.emplace(2, std::make_unique<std::string>("two"));
mapping.emplace(3, std::make_unique<std::string>("three"));
std::cout << *GetOrDefault(mapping, 0,
std::make_unique<std::string>("zero")) << "\n";
std::cout << *GetOrDefault(mapping, 1,
std::make_unique<std::string>("one1")) << "\n";
std::cout << *GetOrDefault(mapping, 3,
new std::string("three1")) << "\n";
}

Edit

I have noticed that the default copy constructor of PossiblyOwner<T> causes undefined behavior, so I had to define a non-default copy and move constructors.

Function to provide a default value when working with std::map

First of all you use operator[] on the map object inside the function. That will never be allowed because it is a non-const function and you have passed the map by const reference. Instead you should rewrite the function implementation to use iterators:

template<typename KeyType, typename ValueType>
ValueType mapDefaultInf(const std::map<KeyType, ValueType> & map, const KeyType & key)
{
const auto it = map.find(key);
return it != map.end() ? it->second : std::numeric_limits<ValueType>::infinity();
}

Second of all you have an ambiguity in the key type. You need to pass in a std::string. Like this:

auto el = mapDefaultInf(map, std::string("alexey"));

Default value of a pointer in a new std::map entry

If the key is not found in the map, the inserted value is value-initialized (§23.4.4.3/1). So no need for a wrapper; the pointer inserted will be a null pointer.

Default value of static std::unordered_map

Yes, it is actually safe to assume that the values inside Foo are always initialized to zero because of the behaviour of operator[]

When the default allocator is used, this results in the key being copy/move constructed from key and the mapped value being value-initialized.

You do not provide a constructor which means that each field in Foo will be value initialized individually which for primitive types means zero initialization.

but

The problem you are actually facing here is that a field called "Apple" does not exist in your map. Unfortunately the semantics of operator[] are such that if the value does not exist, it will be created on the fly. You probably didn't even want to access a non-existent field in the map and you are asking whether it is always initialized to zero so that you can use this fact to check whether the element was there. For this purpose however, you should either use the find() or at() member function.

  • find() will return an iterator pointing to the end of the map if the element does not exist. That means you could guards the element access using

    if (auto apple = map.find("Apple"); apple != map.end()) {
    std::cout << apple->second.num << '\n';
    std::cout << apple->second.state << '\n';
    }

    (with the C++17 if statement initializer)

  • at() will throw an exception if the element is not found.

    std::cout << map.at("Apple").num << '\n';
    std::cout << map.at("Apple").state << '\n';

    This will crash your program with a std::out_of_range exception. You might feel temped to catch this exception to check whether the element existed. Don't do this. It is very bad practice to use exceptions for control flow. On top of that exception are dead slow when they are being thrown.

Initialize std::map, using default-constructed values

You can use value initialization to solve this problem.

struct Foo
{
char x;
};

std::map<int, Foo> myMap =
{
{ 1, {} },
{ 2, {} }
};


Related Topics



Leave a reply



Submit