c++c++17idiomsnoncopyablenonmovable

How to write a ctor for a class owning nocopy-nomove types of which one is to be init.ed from another one that can be one out of several types?


I start from a scenario like this:

At a later point, the need arises that Bar::foo be actually either a Foo1 or a Foo2 depending on the i fed to Bar's constructor.

My initial thought was to use a std::variant, but changing Bar to fulfil the new requirement is not entirely straightforward, because the non-copiable-nor-movable-ity of Foo1, Foo2, and Baz, gets in the way:

The above observations lead me to think that a solution could be using std::unique_ptr, but it does too require quite a bit of code, because one must std::visit in order to construct Baz:

struct Bar {
    using Var = std::variant<std::unique_ptr<Foo1>, std::unique_ptr<Foo2>>;
    Bar(int i)
        : i{i}
        , foo{i == 0 ? Var{std::make_unique<Foo1>(i)} : Var{std::make_unique<Foo2>(i)}}
        , baz{std::visit([](auto&& p) { return Baz{*p}; }, foo)}
    {}
    int i;
    Var foo;
    Baz baz;
};

I was wondering if I have missed some cleaner way to deal with such a scenario.


Solution

  • You can construct the variant in place, no need of std::monostate:

    struct Bar {
        using Foo = std::variant<Foo1, Foo2>;
        Bar(int i)
            : i{i}
            , foo{i == 0 ? Foo(std::in_place_type<Foo1>, i) : Foo(std::in_place_type<Foo2>, i)}
            , baz{std::visit([](auto&& p) { return Baz{p}; }, foo)}
        {}
        int i;
        std::variant<Foo1, Foo2> foo;
        Baz baz;
    };
    

    Demo

    Unfortunately, msvc rejects that code, but using intermediate function (as lambda) works :

    struct Bar {
        using Foo = std::variant<Foo1, Foo2>;
        Bar(int i)
            : i{i}
            , foo{[](int i){
                if (i == 0) {
                    return Foo(std::in_place_type<Foo1>, i);
                } else {
                    return Foo(std::in_place_type<Foo2>, i);
                }
            }(i)}
            , baz{std::visit([](auto&& p) { return Baz{p}; }, foo)}
        {}
        int i;
        std::variant<Foo1, Foo2> foo;
        Baz baz;
    };
    

    Demo