Flatten 2d-array at compiletime
The biggest readability concern, in my opinion, is squashing everything on one line.  Compare:


int main () {
  for (std::size_t i = 0; i &lt; 6; ++i) { std::cout &lt;&lt; flatArray[i] &lt;&lt; " "; } std::cout &lt;&lt; std::endl;
}



to

int main()
{
    for (std::size_t i = 0; i &lt; 6; ++i) {
        std::cout &lt;&lt; flatArray[i] &lt;&lt; " ";
    }
    std::cout &lt;&lt; std::endl;
}


which can then be improved to:

int main()
{
    for (auto v : flatArray) {
        std::cout &lt;&lt; v &lt;&lt; ' ';
    }
    std::cout &lt;&lt; '\n';
}




We can use some shorthand to make nested arrays easier to understand:

namespace detail {
    template &lt;typename T, std::size_t... Dims&gt;
    struct multi_array;

    template &lt;typename T&gt;
    struct multi_array&lt;T&gt; {
        using type = T;
    };

    template &lt;typename T, std::size_t Dim, std::size_t... Dims&gt;
    struct multi_array&lt;T, Dim, Dims...&gt; {
        using type = std::array&lt;
            typename multi_array&lt;T, Dims...&gt;::type, Dim
        &gt;;
    };
}

template &lt;typename T, std::size_t... Dims&gt;
using multi_array = typename detail::multi_array&lt;T, Dims...&gt;::type;




Now, it is more idiomatic to pass the array as an argument instead of a template argument, so that invocation uses more natural syntax and works for both runtime values and compile time values:

template &lt;std::copy_­constructible T, std::size_t N, std::size_t M&gt;
constexpr auto flatten(const multi_array&lt;T, N, M&gt;&amp; array)
    noexcept(std::is_nothrow_copy_constructible_v&lt;T&gt;)
{
    return /* magic */;
}




We can also easily generalize into multiple dimensions from here:

#include &lt;array&gt;
#include &lt;concepts&gt;
#include &lt;type_traits&gt;

namespace detail {
    template &lt;typename T, std::size_t... Dims&gt;
    struct multi_array;

    template &lt;typename T&gt;
    struct multi_array&lt;T&gt; {
        using type = T;
    };

    template &lt;typename T, std::size_t Dim, std::size_t... Dims&gt;
    struct multi_array&lt;T, Dim, Dims...&gt; {
        using type = std::array&lt;
            typename multi_array&lt;T, Dims...&gt;::type, Dim
        &gt;;
    };

    template &lt;typename T&gt;
    struct multi_array_traits {
        static constexpr std::size_t size{1};
        using type = T;
    };

    template &lt;typename T, std::size_t N&gt;
    struct multi_array_traits&lt;std::array&lt;T, N&gt;&gt; {
        static constexpr std::size_t size{N * multi_array_traits&lt;T&gt;::size};
        using type = typename multi_array_traits&lt;T&gt;::type;
    };

    template &lt;std::size_t I&gt;
    constexpr const auto&amp; get(const auto&amp; scalar) noexcept
    {
        static_assert(I == 0);
        return scalar;
    }

    template &lt;std::size_t I, typename T, std::size_t N&gt;
    constexpr const auto&amp; get(const std::array&lt;T, N&gt;&amp; array) noexcept
    {
        constexpr auto InnerSize = multi_array_traits&lt;T&gt;::size;

        constexpr auto Outer = I / InnerSize;
        constexpr auto Inner = I % InnerSize;

        return detail::get&lt;Inner&gt;(array[Outer]);
    }

    template &lt;typename T, std::size_t... Indices&gt;
    constexpr auto flatten(const T&amp; array, std::index_sequence&lt;Indices...&gt;)
    {
        constexpr auto Size = multi_array_traits&lt;T&gt;::size;
        using Elem = typename multi_array_traits&lt;T&gt;::type;

        return std::array&lt;Elem, Size&gt;{Elem(detail::get&lt;Indices&gt;(array))...};
    }
}

template &lt;typename T, std::size_t... Dims&gt;
using multi_array = typename detail::multi_array&lt;T, Dims...&gt;::type;

template &lt;std::copy_constructible T&gt;
constexpr auto flatten(const T&amp; array)
{
    constexpr auto Size = detail::multi_array_traits&lt;T&gt;::size;
    return detail::flatten(array, std::make_index_sequence&lt;Size&gt;{});
}


(live demo)