Kyle Strand Kyle Strand - 3 months ago 13
C++ Question

Convenient type-inferring way to reassign `unique_ptr` value with new object

Is there a convenient way to re-assign the value of a

unique_ptr
with a new owned object, without re-specifying the type?

For instance:

std::unique_ptr<int> foo;
// .... Later, once we actually have a value to store...
foo = std::make_unique<int>(my_cool_value);


Of course
int
is not too much of an eyesore, but
foo::element_type
could be long or subject to change after a refactoring.

So, to use type inference, we could do:

foo = std::make_unique<decltype(foo)::element_type>(value);


...But that's pretty hideous (
foo::element_type
doesn't work because
foo
can't be used in a constant expression).

Ideally,
std::unique_ptr
would support a forwarding
emplace
-like method:

foo.reassign(value);


This would release the old value and, just like
std::vector::emplace
, construct the new owned object in-place.

....But as far as I can tell, there's nothing more concise than
make_unique<decltype(foo)::element_type>
.

EDIT: The most concise way to reassign the value for a type that supports
operator=
is, of course, to use
operator=
:

*foo = value;`


...But I do not want to rely on the copyability of
element_type
(for instance, I initially ran into this issue when trying to work with input-file streams).

Answer

Stash the arguments (or references thereto) into a proxy object with a templated conversion operator that deduces the target type. Then construct the new object once you have that deduced.

template<class... Args>
struct maker {
    template<class T>
    operator std::unique_ptr<T>() && {
        return make<T>(std::index_sequence_for<Args...>());
    }
    std::tuple<Args...> args;
private:  
    template<class T, size_t ... Is>
    std::unique_ptr<T> make(std::index_sequence<Is...>) {
        return std::make_unique<T>(std::get<Is>(std::move(args))...);
    }

};

template<class... Args>
auto maybe_make_unique_eventually(Args&&... args){
    return maker<Args&&...>{std::forward_as_tuple(std::forward<Args>(args)...)};
}