1

Use case: a buffer of records. Here is the basic idea. Making it work requires that the constructor of the record struct knows the key, which is used as recordnumber, when an element is added to the map. Of course, this can be done with more code, but this looks most elegant to me. Minimally coded:

#include <whatever>
struct record
{
    string foo;
    record(unsigned irec) { foo=readrecord(irec); }
};

map<unsigned,record>recbuf;

int main()
{
    // if the element is not yet in the map, it should be read.
    string foo_ten=recbuf[10].foo;
    // do something with the result
    printf("foo_ten: %s\n",foo_ten.c_str());
    return 0;
}

Edit1: code above will not work. Any ideas how to get this to work? Edit2: I derived a mapplus class adding another map::operator[]:

template<class _Kty, class _Ty, class _Pr = less<_Kty>, class _Alloc = allocator<pair<const _Kty, _Ty> > >class mapplus :public map<_Kty, _Ty, _Pr, _Alloc>
{
public:
    mapped_type& operator[](const _Kty &_Keyval)
    {   // find element matching _Keyval or insert with default mapped
        iterator _Where = _Mybase::lower_bound(_Keyval);
        if (_Where == _Mybase::end()
            || _Mybase::_Getcomp()(_Keyval, _Mybase::_Key(_Where._Mynode())))

            _Where = _Mybase::emplace_hint(_Where,
                _Keyval,
                _Ty(_Keyval));
        return (_Where->second);
    }
};

This does work. I am still interested in comments pointing out to me that I did this in a needlessly complex etc. way. Did I? Can it be done with less ado?

Jan
  • 447
  • 6
  • 8

1 Answers1

0

So, you want record objects to be constructed using your record(unsigned) constructor, rather than the default constructor.

Unfortunately, there's no way to do this with operator[] (reference):

If k matches the key of an element in the container, the function returns a reference to its mapped value.

If k does not match the key of any element in the container, the function inserts a new element with that key and returns a reference to its mapped value. Notice that this always increases the container size by one, even if no mapped value is assigned to the element (the element is constructed using its default constructor).

I would not recommend overloading operator[] for std::map, for me it seems like a bad design.

However, you can do it with other methods, like insert or emplace (C++11). See, for example, this answer: Using std::map<K,V> where V has no usable default constructor

Tested example:

#include <map>
#include <sstream>
#include <iostream>


std::string readRecord(int id)
{
    std::stringstream stream;
    stream << id;
    return stream.str();
}

struct Record
{
    Record(int id)
        : foo(readRecord(id))
    {
    }

    std::string foo;
};

int main()
{
    std::map<int, Record> m;
    for (int i = 0; i < 10; ++i)
        m.insert(std::make_pair(i, Record(i)));

    std::cout << m.at(5).foo << std::endl;
    // std::cout << m[7].foo << std::endl;  // error 'Record::Record': no appropriate default constructor available

    return 0;
}
Community
  • 1
  • 1
Aleksei Petrenko
  • 6,698
  • 10
  • 53
  • 87
  • Thanks for pointing out the earlier discussion. It is useful - and has a suggestion for doing pretty much what I did, and references to boost which will be useful.I'll see if I can 'solve' the 'bad design' issue by making it more specific, e.g. integer keys only. Imo this is sufficiently basic to want a solution which also allows square bracket reference. – Jan Jun 13 '16 at 12:11
  • @Jan, just be aware that other programmers may be surprised by non-standard STL container semantics in your program/library, which violates the https://en.wikipedia.org/wiki/Principle_of_least_astonishment. I am not judging or anything, just state my personal opinion :) – Aleksei Petrenko Jun 13 '16 at 12:49