C++: std::optional and std::variant will copy values by default

std::optional and std::variant are attractive alternatives to raw pointers and unions, but they come with a caveat: when you create an optional/variant from a “real” value, a copy will be created. To avoid that, one must use reference_wrapper, and of course be careful not to end up with a dangling reference.

The following code demonstrates that copying occurs when a value is passed to a function accepting a variant:

#include <iostream>
#include <variant>

using namespace std;

struct Foo {
    Foo() { cout << "Foo constructed" << endl; }
    Foo(Foo const& other) { cout << "Foo copied" << endl; }
    ~Foo() { cout << "Foo destroyed" << endl; }
};

struct Bar {
    Bar() { cout << "Bar constructed" << endl; }
    Bar(Bar const& other) { cout << "Bar copied" << endl; }
    ~Bar() { cout << "Bar destroyed" << endl; }
};

void f(variant<Foo,Bar> foobar) {
    auto const* text = holds_alternative<Foo>(foobar) 
        ? "Foo" 
        : (holds_alternative<Bar>(foobar) 
            ? "Bar" 
            : "unknown");
    cout << "f(" << text << ")" << endl;
}

int main()
{
    f(Foo{});
    f(Bar{});

    return 0;
}

Output:

Foo constructed
Foo copied
f(Foo)
Foo destroyed
Foo destroyed
Bar constructed
Bar copied
f(Bar)
Bar destroyed
Bar destroyed

To avoid copying, one must use a reference_wrapper. Note that it is now impossible to pass a temporary:

#include <iostream>
#include <variant>
#include <functional>

using namespace std;

struct Foo {
    Foo() { cout << "Foo constructed" << endl; }
    Foo(Foo const& other) { cout << "Foo copied" << endl; }
    ~Foo() { cout << "Foo destroyed" << endl; }
};

struct Bar {
    Bar() { cout << "Bar constructed" << endl; }
    Bar(Bar const& other) { cout << "Bar copied" << endl; }
    ~Bar() { cout << "Bar destroyed" << endl; }
};

void g(variant<reference_wrapper<const Foo>,reference_wrapper<const Bar>> foobar) {
    auto const* text = holds_alternative<reference_wrapper<const Foo>>(foobar) 
        ? "Foo" 
        : (holds_alternative<reference_wrapper<const Bar>>(foobar) 
            ? "Bar" 
            : "unknown");
    cout << "g(" << text << ")" << endl;
}


int main()
{
    Foo foo;
    Bar bar;
    g(foo);
    g(bar);
    return 0;
}

Output:

Foo constructed
Bar constructed
g(Foo)
g(Bar)
Bar destroyed
Foo destroyed
Posted in

Leave a Reply

Your email address will not be published. Required fields are marked *