How to make_from_tuple on the heap?
Asked Answered
E

5

18

So in C++ there is now make_from_tuple as:

T obj = std::make_from_tuple<T>( { Args... args } ); // args represents a tuple

but how would one do:

T* obj = std::make_new_from_tuple<T*>( { Args... args } );

There is make_shared and make_unique but neither of those takes a tuple (and I'm not sure how one would extract the arguments from the tuple if that is the direction to go, as you can always make_unique then release if you want the raw pointer).

Very simple example 1:

struct A
{
    int i_; double d_; std::string s_;

    A( int i, double d, const std::string& s ) : i_(i), d_(d), s_(s) {}
};

auto aTuple = std::make_tuple( 1, 1.5, std::string("Hello") );

For a more complex example, if the tuple contains a unique_ptr you want to forward, I will want that to work too.

Exarchate answered 15/12, 2022 at 9:3 Comment(0)
W
15

You can basically copy the implementation from cppreference's Possible implementation, chuck in a make_unique and it just works:

#include <string>
#include <memory>

namespace detail {
template<class T, class Tuple, std::size_t... I>
constexpr std::unique_ptr<T> make_new_from_tuple_impl(Tuple&& t, std::index_sequence<I...>)
{
    static_assert(std::is_constructible_v<T,
        decltype(std::get<I>(std::declval<Tuple>()))...>);
#if __cpp_lib_reference_from_temporary >= 202202L
    if constexpr (std::tuple_size_v<std::remove_reference_t<Tuple>> == 1) {
        using tuple_first_t = decltype(std::get<0>(std::declval<Tuple>()));
        static_assert(!std::reference_constructs_from_temporary_v<T, tuple_first_t>);
    }
#endif
    return std::make_unique<T>(std::get<I>(std::forward<Tuple>(t))...);
}
} // namespace detail

template<class T, class Tuple>
constexpr std::unique_ptr<T> make_new_from_tuple(Tuple&& t)
{
    return detail::make_new_from_tuple_impl<T>(std::forward<Tuple>(t),
        std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}

struct A
{
    int i_; double d_; std::string s_;

    A( int i, double d, const std::string& s ) : i_(i), d_(d), s_(s) {}
};

int main()
{
    auto aTuple = std::make_tuple( 1, 1.5, std::string("Hello") );

    auto a = make_new_from_tuple<A>(std::move(aTuple));
}

Or with raw pointers:

#include <string>
#include <memory>

namespace detail {
template<class T, class Tuple, std::size_t... I>
constexpr T* make_new_from_tuple_impl(Tuple&& t, std::index_sequence<I...>)
{
    static_assert(std::is_constructible_v<T,
        decltype(std::get<I>(std::declval<Tuple>()))...>);
#if __cpp_lib_reference_from_temporary >= 202202L
    if constexpr (std::tuple_size_v<std::remove_reference_t<Tuple>> == 1) {
        using tuple_first_t = decltype(std::get<0>(std::declval<Tuple>()));
        static_assert(!std::reference_constructs_from_temporary_v<T, tuple_first_t>);
    }
#endif
    return new T(std::get<I>(std::forward<Tuple>(t))...);
}
} // namespace detail

template<class T, class Tuple>
constexpr T* make_new_from_tuple(Tuple&& t)
{
    return detail::make_new_from_tuple_impl<T>(std::forward<Tuple>(t),
        std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}

struct A
{
    int i_; double d_; std::string s_;

    A( int i, double d, const std::string& s ) : i_(i), d_(d), s_(s) {}
};

int main()
{
    auto aTuple = std::make_tuple( 1, 1.5, std::string("Hello") );

    auto a = make_new_from_tuple<A>(std::move(aTuple));
}
Willpower answered 15/12, 2022 at 9:34 Comment(1)
An adaptation of this solved it for me perfectly. And has solved what was for me a 16-year old queryExarchate
I
9

You can use std::apply. It unpacks arguments from a tuple and passes them to a callable. You just need to wrap the constructor.

#include <tuple>
#include <string>

struct A
{
    int i_; double d_; std::string s_;

    A( int i, double d, const std::string& s ) : i_(i), d_(d), s_(s) {}
};


int main() {
    auto aTuple = std::make_tuple( 1, 1.5, std::string("Hello") );
    A* a = std::apply( [](int i,double d,const std::string& s) { return new A(i,d,s);},aTuple);
}

Perfect forwarding omitted for the sake of brevity.

Irradiate answered 15/12, 2022 at 9:34 Comment(0)
J
8

Simply call new with it:

T* obj = new auto(std::make_from_tuple<T>( { Args... args } ));

// unique ptr
std::unique_ptr obj{new auto(std::make_from_tuple<T>({ Args... args }))};

// shared ptr
std::shared_ptr obj{new auto(std::make_from_tuple<T>({ Args... args }))};

// doesn't work for make_shared unfortunately
auto obj = std::apply([](auto&&... args) {
    return std::make_shared<T>(std::forward<decltype(args)>(args)...);
}, { Args... args });

Because make_from_tuple returns a prvalue, this will construct the object directly in the heap with no copies or move.

Jemima answered 15/12, 2022 at 15:49 Comment(1)
This option doesn't work if the class has a protected destructor to forbid stack allocation.Depurate
A
4

Explicit use of new and delete is considered a bad practice since C++11/C++14. So use of std::make_unique (C++14) and std::make_shared (C++11) is recommended.

template<typename T, typename...Args>
std::shared_ptr<T> make_shared_from_tuple(const std::tuple<Args...>& t)
{
    return std::apply([](auto...args){ return std::make_shared<T>(args...); }, t);
}

template<typename T, typename...Args>
std::unique_ptr<T> make_unique_from_tuple(const std::tuple<Args...>& t)
{
    return std::apply([](auto...args){ return std::make_unique<T>(args...); }, t);
}

https://godbolt.org/z/6zjTqK8f1

Probably it would be nice to provide also some overloads to handle move semantics.

Addia answered 15/12, 2022 at 10:13 Comment(0)
D
0

The simplest I can write is:

template<typename obj, typename tup>
auto unique_from_tuple(tup&& t) {
    return std::apply(
        [] (auto&& ... args) {
            return std::make_unique<obj>
                (std::forward<decltype(args)>(args)...);
        },
        std::forward<tup>(t)
    );
};


// Create a unique pointer of type obj:
auto x =unique_from_tuple<obj>(y,z,w);
Dahlia answered 19/12, 2022 at 21:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.