9

How can one initialize static map, where value is std::unique_ptr?

static void f()
{
    static std::map<int, std::unique_ptr<MyClass>> = {
        { 0, std::make_unique<MyClass>() }
    };
}

Of course this does not work (copy-ctor of std::unique_ptr is deleted).

Is it possible?

vladon
  • 8,158
  • 2
  • 47
  • 91

3 Answers3

10

The Problem is that constructing from std::initializer-list copies its contents. (objects in std::initializer_list are inherently const). To solve your problem: You can initialize the map from a separate function...

std::map<int, std::unique_ptr<MyClass>> init(){
    std::map<int, std::unique_ptr<MyClass>> mp;
    mp[0] = std::make_unique<MyClass>();
    mp[1] = std::make_unique<MyClass>();
    //...etc
    return mp;
}

And then call it

static void f()
{
    static std::map<int, std::unique_ptr<MyClass>> mp = init();
}

See it Live On Coliru

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • Will it be a difference if `init()` will return `std::map<...>&&` (rvalue)? I.e. is returning an rvalue will be better? Upd: with rvalue there will be a segfault – vladon Jul 05 '16 at 21:51
  • 1
    @vladon, doing that is not just a performance perssimization, that's also a bug. You will be returning an `rvalue` reference to a destroyed object. See [this](http://stackoverflow.com/questions/4986673/c11-rvalues-and-move-semantics-confusion-return-statement) ... Returning by value is extremely efficient in cases like this, because, its [certainly(C++17)](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html) going to enjoy [RVO](http://en.cppreference.com/w/cpp/language/copy_elision) – WhiZTiM Jul 05 '16 at 21:56
2

Writing bespoke crestion code seems boring and gets in the way of clarity.

Here is reasonably efficient generic container initialization code. It stores your data in a temporary std::array like an initializer list does, but it moves out instead of making it const.

The make_map takes an even number of elements, the first being key the second value.

template<class E, std::size_t N>
struct make_container_t{
  std::array<E,N> elements;
  template<class Container>
  operator Container()&&{
    return {
      std::make_move_iterator(begin(elements)),
      std::make_move_iterator(end(elements))
    };
  }
};
template<class E0, class...Es>
make_container_t<E0, 1+sizeof...(Es)>
make_container( E0 e0, Es... es ){
  return {{{std::move(e0), std::move(es)...}}};
}

namespace details{
  template<std::size_t...Is, class K0, class V0, class...Ts>
  make_container_t<std::pair<K0,V0>,sizeof...(Is)>
  make_map( std::index_sequence<Is...>, std::tuple<K0&,V0&,Ts&...> ts ){
    return {{{
      std::make_pair(
        std::move(std::get<Is*2>(ts)),
        std::move(std::get<Is*2+1>(ts))
      )...
    }}};
  }
}
template<class...Es>
auto make_map( Es... es ){
  static_assert( !(sizeof...(es)&1), "key missing a value?  Try even arguments.");
  return details::make_map(
    std::make_index_sequence<sizeof...(Es)/2>{},
    std::tie( es... )
  );
}

This should reduce it to:

static std::map<int, std::unique_ptr<MyClass>> bob = 
  make_map(0, std::make_unique<MyClass>());

... barring typos.

Live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • @U.Bulle I fixed 2 small typos and added a link to a live example that compiles. – Yakk - Adam Nevraumont Dec 16 '18 at 22:23
  • I misclicked and removed my comment. For sake of history, I commented on this pointing out a compilation fail on unpacking command in details::make_map and that it gives me std::pair error. You're right it compiles now. I'd love to be able to use it on my code too though, I don't know what's causing it to fail in my case here https://stackoverflow.com/questions/53806687/, it's probably a bug on VStudio compiler. Thank you anyway though, very interesting and cool solution! – U. Bulle Dec 16 '18 at 22:41
  • @U.Bulle [It compiles in MSVC](https://godbolt.org/z/SlT34X)? I have no idea why it won't compile for you. – Yakk - Adam Nevraumont Dec 16 '18 at 22:45
1

another way to do this is to use a lambda. it's the same as using a separate function but puts the map's initialisation closer to the action. In this case I've used a combination of an auto& and decltype to avoid having to name the type of the map, but that's just for fun.

Note that the argument passed into the lambda is a reference to an object that has not yet been constructed at the point of the call, so we must not reference it in any way. It's only used for type deduction.

#include <memory>
#include <map>
#include <utility>

struct MyClass {};


static auto& f()
{
  static std::map<int, std::unique_ptr<MyClass>> mp = [](auto& model)
  {
    auto mp = std::decay_t<decltype(model)> {};
    mp.emplace(0, std::make_unique<MyClass>());
    mp.emplace(1, std::make_unique<MyClass>());
    return mp;
  }(mp);
  return mp;
}

int main()
{
  auto& m = f();
}

Here's another way. In this case we've passed a temporary into the lambda and relied on copy elision/RVO.

#include <memory>
#include <map>
#include <utility>

struct MyClass {};

static auto& f()
{
  static auto mp = [](auto mp)
  {
    mp.emplace(0, std::make_unique<MyClass>());
    mp.emplace(1, std::make_unique<MyClass>());
    return mp;
  }(std::map<int, std::unique_ptr<MyClass>>{});
  return mp;
}

int main()
{
  auto& m = f();
}

And yet another way, using a lambda capture in a mutable lambda.

#include <memory>
#include <map>
#include <utility>

struct MyClass {};

static auto& f()
{
  static auto mp = [mp = std::map<int, std::unique_ptr<MyClass>>{}]() mutable
  {
    mp.emplace(0, std::make_unique<MyClass>());
    mp.emplace(1, std::make_unique<MyClass>());
    return std::move(mp);
  }();
  return mp;
}

int main()
{
  auto& m = f();
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142