Search code examples
c++tuplesvariadic-templates

How to make_from_tuple on the heap?


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.


Solution

  • 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));
    }