A cartesian product of tuples in C++17
You actually hint at the primary concern with your note about std::make_tuple(), std::tuple_cat() and gradually building up the result.
The point is that you have much copying with a plethora of temporaries going on.

Also, quite a bit of recursion.

Use index_sequences, the power of template parameter pack expansion, and forwarding using std::forward_as_tuple() and you can flatten it, as well as only copy directly from the source to the destination without intermediate steps.

While you are at it, ponder noexcept.

#include &lt;utility&gt;
#include &lt;tuple&gt;

namespace detail {
    template &lt;class T&gt;
    constexpr std::size_t tuple_size_v = std::tuple_size_v&lt;std::decay_t&lt;T&gt;&gt;;

    template &lt;std::size_t I, class T, std::size_t... N&gt;
    static constexpr auto index_helper(std::index_sequence&lt;N...&gt;) noexcept {
        return (1 * ... * (I &lt; N ? tuple_size_v&lt;std::tuple_element_t&lt;N, T&gt;&gt; : 1));
    }
    template &lt;std::size_t N, std::size_t I, class T&gt;
    static constexpr auto index() noexcept {
        return N
            / index_helper&lt;I, T&gt;(std::make_index_sequence&lt;tuple_size_v&lt;T&gt;&gt;())
            % tuple_size_v&lt;std::tuple_element_t&lt;I, T&gt;&gt;;
    }

    template &lt;std::size_t N, class T, std::size_t... I&gt;
    static constexpr auto cartesian_product(T t, std::index_sequence&lt;I...&gt;) noexcept {
        return std::forward_as_tuple(std::get&lt;index&lt;N, I, T&gt;()&gt;(std::get&lt;I&gt;(t))...);
    }
    template &lt;class T, std::size_t... N&gt;
    static constexpr auto cartesian_product(T t, std::index_sequence&lt;N...&gt;) noexcept {
        return std::make_tuple(cartesian_product&lt;N&gt;(t, std::make_index_sequence&lt;tuple_size_v&lt;T&gt;&gt;())...);
    }

    template &lt;class T&gt;
    auto tuple_no_ref(T t)
    { return std::apply([](auto&amp;&amp;... x){ return std::make_tuple(x...); }, t); }
    template &lt;class T&gt;
    auto tuple2_no_ref(T t)
    { return std::apply([](auto&amp;&amp;... x){ return std::make_tuple(tuple_no_ref(x)...); }, t); }
}

template &lt;class... T&gt;
constexpr auto cartesian_product_ref(T&amp;&amp;... t) noexcept {
    constexpr auto N = sizeof...(T) ? (1 * ... * detail::tuple_size_v&lt;T&gt;) : 0;
    return detail::cartesian_product(std::forward_as_tuple(t...), std::make_index_sequence&lt;N&gt;());
}

template &lt;class... T&gt;
constexpr auto cartesian_product(T&amp;&amp;... t)
noexcept(noexcept(decltype(detail::tuple2_no_ref(cartesian_product_ref(t...)))(cartesian_product_ref(t...))))
{
    auto r = cartesian_product_ref(t...);
    using R = decltype(detail::tuple2_no_ref(r));
    return R(r);
}