November 15th, 2024

How do I put a non-copyable, non-movable, non-constructible object into a std::optional?

Last time, we wondered how you could put an object into a std::optional<T> if T is not copyable, not movable, and not constructible. You typically run into this case when T is an object that comes from a factory method rather than a constructor.

For expository purposes, say we have a Widget that can be created inside a region or outside a region. One possible design would be to have a constructor that takes a bool that say whether to go inside or outside.

struct Widget
{
    Widget(Region const& region, bool inside);

    Widget(Widget const&) = delete;
    Widget(Widget &amp&) = delete;
    Widget& operator=(Widget const&) = delete;
    Widget& operator=(Widget &amp&) = delete;
};

I’m not a fan of this design because you have to remember what that final bool means.

Widget widget(region, true); // what does "true" mean?

Does that true mean “initially enabled”? Does it mean “inside”? Does it mean “outside”?

To remove ambiguity, we can switch to factory methods.

struct Widget
{
    Widget() = delete;
    Widget(Widget const&) = delete;
    Widget(Widget &amp&) = delete;
    Widget& operator=(Widget const&) = delete;
    Widget& operator=(Widget &amp&) = delete;

    static Widget CreateInside(Region const& region);
    static Widget CreateOutside(Region const& region);
private:
    ⟦ ... ⟧
};

Okay, so how can we put a Widget inside a std::optional<Widget>? All of our tools for putting an object into an optional are failing us. We can’t use emplace: That will try to construct the Widget from the thing we passed to emplace, but Widget is not constructible!

The trick is that the std::optional constructor and assignment operator create the T as if by non-list-initialization. This means that implicit conversion operators are in play!

struct WidgetInsideRegionCreator
{
    WidgetCreator(Region const& region) : m_region(region) {}
    operator Widget() { return Widget::CreateInside(m_region); }
    Region const& m_region;
};

void sample(Region const& region)
{
    // construct with a Widget value
    std::optional<Widget> o(WidgetInsideRegionCreator(region));

    // or place a Widget into the optional
    o.emplace(WidgetInsideRegionCreator(region));
}

The idea here is that we create a helper object, the Widget­Inside­Region­Creator, which supports an implicit conversion to Widget via the factory method. The Widget can then be initialized from the helper object by conversion. The return value from the conversion operator is placed directly in the optional‘s Widget thanks to mandatory copy elision.

Okay, now that we know what to do, we can generalize it, so you don’t have to create dozens of tiny little creator classes.

template<typename F>
struct EmplaceHelper
{
    EmplaceHelper(F&& f) : m_f(f) {}
    operator auto() { return m_f(); }
    F& m_f;
};

void sample(Region const& region)
{
    // construct with a Widget value
    std::optional<Widget> o(
        EmplaceHelper([&] {
            return Widget::CreateInside(region);
        }));

    // or place a Widget into the optional
    o.emplace(EmplaceHelper([&] {
            return Widget::CreateInside(region);
        }));
}

This trick works even if the factory method belongs to another object.

struct WidgetFactory
{
    Widget CreateInside(Region const& region) const;
};

void sample(WidgetFactory const& factory,
            Region const& region)
{
    // construct with a Widget value
    std::optional<Widget> o(
        EmplaceHelper([&] {
            return factory.CreateInside(region);
        }));

    // or place a Widget into the optional
    o.emplace(EmplaceHelper([&] {
            return factory.CreateInside(region);
        }));
}

If you don’t like lambdas, you can try invoke-oriented programming.

template<typename F, typename... Args>
struct EmplaceHelper
{
    EmplaceHelper(F&& f, Args&&... args)
        : m_f(f), m_args((Args&&)args...) {}
    operator auto()
        { return std::apply(m_f, m_args); }
    F& m_f;
    std::tuple<Args&&...> m_args;
};

template<typename F, typename... Args>
EmplaceHelper(F&&, Args&&...) -> EmplaceHelper<F, Args...>;

void sample(WidgetFactory const& factory,
            Region const& region)
{
    // construct with a Widget value
    std::optional o(
        EmplaceHelper(Widget::CreateInside, region));

    // or place a Widget into the optional
    o.emplace(EmplaceHelper(&WidgetFactory::CreateInside,
                            factory, region));

    // lambdas still work
    o.emplace(EmplaceHelper([&] {
            return factory.CreateInside(region);
        }));
}
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

6 comments

Discussion is closed. Login to edit/delete existing comments.

  • Maxime Casas

    This is a clever trick.

    It got me wondering: in what situations does one want to put a non-copiable non-movable object into a std::optional by value?

    Most such objects I have seen in C++ were used to represent objects with an "identity", such as widgets just like in your example, and were only manipulated by pointer/reference. But when put by value inside an optional, it becomes impossible to move the object out of the optional containing the object returned by the factory method. Does it mean the code calling the factory has to keep the widget (to reuse your widget example) inside...

    Read more
  • BENOIT Frederic

    Wait, functions can actually return non-movable, non-copyable objects?

  • Weiß · Edited

    Does that true mean “initially enabled”? Does it mean “inside”? Does it mean “outside”?
    To remove ambiguity, we can switch to factory methods.

    Could've just replaced the bool with a scoped enum, no? ;)

    Even if two separate function definitions are desired, overload resolution seems more fitting than the solution described above:

    <code>
    Or shorter yet:
    <code>
    I end up believing that the use of factory methods instead of constructors is the root of all evil here. It might be nice to have 'named' constructors like this, but that could complicate a lot of other things.

    Read more
    • Raymond ChenMicrosoft employee Author · Edited

      The enum works only if the Inside and Outside constructors take identical parameters. If they take different parameters, then you run into trouble. E.g.

      enum Placement { Above, Below };
      
      Widget(Region const& region); /* create widget inside */
      Widget(Region const& region, Placement placement); /* create a widget outside, with placement */
      

      . A enum WidgetPlacement { Inside, Outside } forces you into adding a dummy placement parameter to the inside version.

      • Weiß

        Thank you for the response!
        But if your separate case constructors already have differing signatures, then is there really any ambiguity to be removed by adding factory methods? Does this ambiguity justify the complexity of the described solution (for emplacing an object with no public constructors)?

        Of course my point is moot if the class in question is part of a static API. In which case using a template proxy type with an implicit conversion operator deferring to the factory is an elegant solution.

        But if not, and ambiguity is really the only issue, I would at least consider packaging your parameters...

        Read more
  • LB

    Clever, I wouldn’t have thought to utilize conversions and RVO for this!