Search code examples
c++templatesinheritancestatic-initialization

Initializing a static std::map<int, unique_ptr<int>> in C++


This is a similiar question to this post. The answer that I think has the most promise has to do with templated static initialization. Here is the class from that answer:

template <typename T, typename U>
class create_map
{
private:
    std::map<T, U> m_map;
public:
    create_map(const T& key, const U& val)
    {
        m_map[key] = val;
    }

    create_map<T, U>& operator()(const T& key, const U& val)
    {
        m_map[key] = val;
        return *this;
    }

    operator std::map<T, U>()
    {
        return m_map;
    }
};

Usage:

std::map mymap = create_map<int, int >(1,2)(3,4)(5,6);

This works great for a structure or a class as well as the base types. What I would like to do is to use this with a unique_prt<Structure\Class> as the value like this:

std::map mymap = create_map<DWORD, std::unique_ptr<Structure|Class>>(1, new Structure|Class())(2, new Structure|Class())

I am trying to use a template class so that I can have the value be any type. I got the idea from this post to use an interface as a base class and then a template derived class to hold any type of value. So those classes look like this:

class MyFieldInterface
{
public:
    int m_Size;
    virtual ~MyFieldInterface() = default;
}

template <typename T>
class MyField : public MyFieldInterface {
    T m_Value; 
}

Then the map can be setup like I described earlier:

std::map<DWORD, unique_ptr<MyFieldInterface>> mymap;

But trying to initialize it with create_map fails:

std::map mymap = create_map<DWORD, unique_ptr<MyFieldInterface>>(1, new MyField<DWORD>())(2, new MyField<char>())(3, new MyField<WORD>())

The error that I get is this:

operator()
Error: no instance of constructor "create_map<T, U>::create_map [with T=DWORD, U=std::unique_ptr<MyFieldInterface, std::default_delete<MyFieldInterface>>]" matches the argument list
argument types are: (DWORD, MyField<DWORD>*)

So I thought that I need a constructor and an operator() that can handle the pointer properly. I added both to the class:

create_map(const T& key, const U* val)
{
    m_map[key] = val;
}

create_map<T, U>& operator()(const T& key, const U* val)
{
    m_map[key] = val;
    return *this;
}

I got the same error. So I tried without the *:

create_map(const T& key, const U val)
{
    m_map[key] = val;
}

create_map<T, U>& operator()(const T& key, const U val)
{
    m_map[key] = val;
    return *this;
}

I got the same error. As I'm writing this, I realized that the problem may be related to the inheritance and not necessarily create_map's operator. Can you help me figure out the operator() definition or the base/derived class definition that I need to get this to work?

Please limit your answers to not include the Boost C++ libraries as I am not allowed to use them here at work.

Edit: updated MyFieldInterface as requested by T.C.


Solution

  • This is one possible implementation:

    template <typename T, typename U>
    class create_map
    {
    private:
        std::map<T, U> m_map;
    public:
        create_map(T key, U val)
        {
            m_map.emplace(std::move(key), std::move(val));
        }
    
        create_map&& operator()(T key, U val) &&
        {
            m_map.emplace(std::move(key), std::move(val));
            return std::move(*this);
        }
    
        operator std::map<T, U>() &&
        {
            return std::move(m_map);
        }
    };
    

    Note the taking argument by value and then moving it into the map with emplace, and the conversion operator that moves from m_map.

    I don't know if MSVC 2012 supports ref-qualifiers. If it doesn't, you'll need to remove it (that's the two &&s after the function parameter list). The point of that is to enforce that create_map should only be used as a temporary. It is possible to also enforce that the conversion operator is only called once, but I didn't do that in the code above.

    Now your calls cannot use naked news because 1) it isn't exception-safe and 2) raw pointers cannot be implicitly converted to unique_ptrs. A simple make_unique implementation that doesn't take arrays into account is

    namespace util {
        template<class T, class... Args>
        std::unique_ptr<T> make_unique(Args&&... args) {
            return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
        }
    }
    

    You can then change the new MyField<DWORD>()s to util::make_unique<MyField<DWORD>>()*.

    Demo.


    * Using a qualified call disables ADL, which can have surprising effects when you upgrade your compiler if your call has arguments. A full implementation of make_unique according to the spec can be found in the example code in N3656, the make_unique proposal paper.