Initialize static std::map with non copyable value

2020-07-06 02:15发布

I'd like to initialize a static std::map where the value is not copyable. I'll call my class ValueClass. ValueClass has an std::unique_ptr as private member and I even ensure that ValueClass is not copyable by extending non_copyable that looks like the following:

class non_copyable {
public:
    non_copyable() = default;
protected:
    virtual ~non_copyable() = default;
private:
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

Now I'm trying to define a std::map using my class as value:

static std::map<int, ValueClass> value_classes = {
    {0, ValueClass()},
    {1, ValueClass() }
};

I get compilation error as initializer_list tries to copy this class.

I've tried to write my own make_map function whole this weekend during many hours to enable initialization without copying but I've failed. I've tried this, that and other but none of them compile with Visual Studio 15.9.4.

How can I initialize static std::map where copy is not forced, and the initialization is uniformed in one function, using Visual Studio compiler?

EDIT: Here is the simplified version of the real life scenario where I'm trying to get this working (forgive me for lack of naming convention and inconsistency for cases):

#include <iostream>
#include <map>

class non_copyable {
public:
    non_copyable() = default;
protected:
    virtual ~non_copyable() = default;
private:
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

class InnerValueClass : public non_copyable
{
public:
    InnerValueClass(const int inner_number) : inner_number_(inner_number) {  }
private:
    int inner_number_;
};

class ValueClass : public non_copyable
{
public:
    ValueClass(const int number1) : number1_(number1) {  }
    ValueClass(const bool condition) : condition_(condition), inner_value_(
        std::make_unique<InnerValueClass>(5)) {  }
private:
    int number1_{};
    bool condition_{};
    std::unique_ptr<InnerValueClass> inner_value_{};
};

/* Inline initialization of std::map copies, this is for initialization of non-copy types*/
template <typename TKey, typename TNonCopyableValue>
class make_map_by_moving
{
    typedef std::map<TKey, TNonCopyableValue> map_type;
    map_type map_;
public:
    make_map_by_moving(const TKey& key, TNonCopyableValue&& val)
    {
        map_.emplace(key, std::move(val));
    }
    make_map_by_moving<TKey, TNonCopyableValue>& operator()(const TKey& key, TNonCopyableValue&& val)
    {
        map_.emplace(key, std::move(val));
        return *this;
    }
    operator const map_type&()
    {
        return map_;
    }
};

static std::map<int, ValueClass> map =
        make_map_by_moving<int, ValueClass>
                (1, ValueClass(5))
                (2, ValueClass(true));
/* It goes on like this for hundreds of lines, so I really appreciate any
solution that leave me with a clean initialization rather than calling
functions on std::map */

int main() { }

Duplicate edit: The solution provided in that question does not work the class structure I have. I'm also looking for a solution to fix make_map_by_moving function in other words an inline initialization, the answer provided there is an imperative solution with function calls.

3条回答
Explosion°爆炸
2楼-- · 2020-07-06 02:35

You simply can not use initializer_list to move an object from a non-copyable object.

Your class deletes the copy constructor & assignment operator. When you try to initialize your map or any other container with an initializer_list the initializer_list strictly forces you to reference an LValue and forbids RValue move or forward semantics.

Here is a very nice blog article that explains all of the details: knatten.org as well as a similar Q/A found here.

查看更多
在下西门庆
3楼-- · 2020-07-06 02:42

You cannot do this directly, because initializer_list has const backing for all of its elements - and they have to be copied from the initializer list into the container. That, obviously, requires copying. There's no way to emplace from an initializer list unfortunately.

In C++17, thanks to guaranteed copy elision, you can do this:

std::map<int, non_copyable> get() {
    std::map<int, non_copyable> m;
    m.emplace(std::piecewise_construct, std::tuple(0), std::tuple());
    m.emplace(std::piecewise_construct, std::tuple(1), std::tuple());
    return m;
}

std::map<int, non_copyable> value_classes = get();

This code performs no copies on non_copyable. We emplace construct inside of the map, and then beacuse get() is a prvalue, there is no copy/move from get() into value_classes. The m within get() is the object value_classes.

A slightly sneaker approach would be to abuse try_emplace() for this:

std::map<int, non_copyable> get() {
    std::map<int, non_copyable> m;
    m.try_emplace(0);
    m.try_emplace(1);
    return m;
}

try_emplace() takes the key type by itself (so you can just pass an int) and then the arguments for the value for emplacing separately, which makes for a much less verbose way of accomplishing this.

查看更多
做自己的国王
4楼-- · 2020-07-06 02:53

I think you need to create the object with insert_or_assign in a function and then return it:

std::map<int, ValueClass> populate()
{
    std::map<int, ValueClass> value_classes;
    value_classes.insert_or_assign(std::make_pair(0, ValueClass());
    return value_classes;
}

And your initialization becomes:

std::map<int, ValueClass> value_classes = populate();

But then, this class has a virtual destructor, which means that you want actually may actually be a std::map<int, std::unique_ptr<ValueClass>> and not a map of actual objects (not sure what these objects are going to be used for?).

Edit after the question edit:

In this case, Barrys suggestion is the one to follow, usingemplace`:

std::map<int, ValueClass> populate()
{
    std::map<int, ValueClass> value_classes;
    value_classes.emplace(1, 5);
    return value_classes;
}

Also include functional.

查看更多
登录 后发表回答