Потому что все размеры кроме последнего обязательно фиксированы. Как только вам нужны обе размерности переменными, приходится переходить к более общей схеме на основе одномерного массива.
Вы, вероятно, имеете в виду статические массивы (поскольку в C++ нет многомерных динамических массивов). Однако про статические массивы многое известно уже на этапе компиляции, поэтому все размеры можно "освободить" с помощью шаблонов:
#include <iostream>
#include <iomanip>
template <size_t n, size_t m>
void fill_array(int (&x)[n][m])
{
    int k = 0;
    
    for (size_t i = 0; i < n; ++i)
    {
        for (size_t j = 0; j < m; ++j)
        {
            x[i][j] = ++k;
        }
    }
}
template <size_t n, size_t m>
void print_array(const int (&x)[n][m])
{
    std::cout << "n = " << n << ", m = " << m << std::endl;
    for (size_t i = 0; i < n; ++i)
    {
        for (size_t j = 0; j < m; ++j)
        {
            std::cout << std::setw(5) << x[i][j];
        }
        std::cout << std::endl;
    }
    std::cout << std::endl;
}
int main()
{
    int a[2][5];
    int b[4][3];
    
    fill_array(a);
    print_array(a);
    fill_array(b);
    print_array(b);
}
  Щас проверил на простом тесте на gcc. Вариант с многомерным массивом чуть быстрее при рандомном доступе и сильно быстрее при последовательном (хотя, конечно, если при последовательном доступе в "одномерном" варианте добавить бегущий пойнтер, то наверно будет одинаково).
Что ж, теперь можно расслабиться и спокойно продолжать пользоваться многомерными массивами. 
