C++ Initialization by making a copy or using reference depending on the condition

Viewed 78

І have a bool variable named do_copy.

If do_copy == true, I want to work with a copy, like
some_type model = input_model

If do_copy == false, I want to work with a reference, like
some_type& model = input_model

Code that does what I want:

some_type copy_model;
if (do_copy)
    copy_model = input_model;
some_type& model = do_copy ? copy_model : input_model;

It does not look very good, and I call the default constructor, which isn't necessary for do_copy == false.

Is there a way to make it more elegant?

3 Answers

One way to avoid the unnecessary constructor is to only allocate copy_model dynamically if it's needed:

std::unique_ptr<some_type> copy_model;
if (do_copy)
    copy_model = std::make_unique<some_type>(input_model);
some_type& model = copy_model ? *copy_model : input_model;

I'm not sure that's much more elegant though.

Move the logic that uses model into a separate function:

void doSomething(some_type& model)
{
    //...
}

...

if (do_copy)
{
    some_type copy_model = input_model;
    doSomething(copy_model);
}
else
    doSomething(input_model);

Demo

Alternatively, in C++11 and later:

template<typename T>
void doSomething(T&& model) // T will be lvalue-ref or rvalue-ref depending on input
{
    //...
}

...

if (do_copy)
    doSomething(some_type{input_model});
else
    doSomething(input_model);

Demo

You might make the copy as part of the ternary expression:

some_type copy_model;
some_type& model =
    do_copy
    ? (copy_model = input_model, copy_model)
    : (input_model);

This still requires default constructing copy_model even when do_copy is false. If your default constructor is really expensive, you might use an optional:

std::optional<some_type> copy_model;
some_type& model =
    do_copy
    ? (copy_model = input_model, *copy_model)
    : (input_model);

Note that this doesn't come completely for free: the destructor of std::optional<some_type> has an extra branch as compared to some_type.

And if you find yourself needing to do this very often, why not wrap this pattern in a class:

template<class T>
struct copy_or_reference
{
    std::optional<T> maybeCopy;
    T& ref;

    copy_or_reference(copy_tag, T& x)
        : ref(x)
    {};

    copy_or_reference(reference_tag, const T& x)
        : maybeCopy(x), ref(*maybeCopy)
    {};

    operator T& () const { return ref; };
    operator T& () { return ref; };
};

template<class T>
auto make_copy_or_reference(bool copy, T& x)
{
    return copy ? copy_or_reference<T>(copy_tag{}, x) : copy_or_reference<T>(reference_tag{}, x);
}


void do_something(some_type&);

void do_something_else()
{
    ...
    copy_or_reference<some_type> model = make_copy_or_reference(do_copy, input_model);
    do_something(model);
}
Related