Trying for an efficient and readable merge-sort implementation My first implementation of merge sort in C++.
I'm looking for any sort of optimization and/or conventional practice tips, such as using the appropriate data types and naming my variables appropriately.
#include &lt;iostream&gt;

void print_array(int[], size_t);

void merge_sort(int[], size_t, size_t);

int main() {
    size_t size;
    
    std::cout &lt;&lt; &quot;Enter the size of the container: &quot;;
    std::cin &gt;&gt; size;
    
    int arr[size];
    for (size_t i = 0; i &lt; size; ++i) {
        std::cout &lt;&lt; &quot;[&quot; &lt;&lt; i+1 &lt;&lt; &quot;] Enter the next integer element: &quot;;
        std::cin &gt;&gt; arr[i];
    }
    
    std::cout &lt;&lt; std::endl &lt;&lt; &quot;Unsorted array: &quot;;
    print_array(arr, size);
    std::cout &lt;&lt; std::endl &lt;&lt; std::endl;
    
    merge_sort(arr, 0, size-1);
    
    std::cout &lt;&lt; &quot;Sorted array: &quot;;
    print_array(arr, size);
}

void print_array(int arr[], size_t size) {
    for (size_t i = 0; i &lt; size; ++i) {
        std::cout &lt;&lt; arr[i] &lt;&lt; ' ';
    }
}

void merge_sort(int arr[], size_t start, size_t end) {
    if (end &gt; start) {
        size_t middle = (start+end)/2;
        
        merge_sort(arr, start, middle);
        merge_sort(arr, middle+1, end);
        
        int l[middle-start+1];
        int r[end-middle];
        
        for (size_t i = start; i &lt;= middle; ++i) {
            l[i-start] = arr[i];
        }
        
        for (size_t i = middle+1; i &lt;= end; ++i) {
            r[i-(middle+1)] = arr[i];
        }
        
        size_t i = 0, j = 0, k = start;
        while (i &lt; middle-start+1 &amp;&amp; j &lt; end-middle) {
            if (l[i] &lt; r[j]) {
                arr[k] = l[i++];
            } else {
                arr[k] = r[j++];
            }
            ++k;
        }
        
        for (size_t z = i; z &lt; middle-start+1; ++z) {
            arr[k] = l[z];
            ++k;
        }
        
        for (size_t z = j; z &lt; end-middle; ++z) {
            arr[k] = r[z];
            ++k;
        }
    }
}
The single most important advantage of merge sort is its stability. The elements compared equal retain their original order. Your code loses stability. If l[i] compares equal to r[j], the latter is merged first.

Your code assumes closed ranges (that is, end is a valid index). That leads to some unpleasant fiddling with indices. Semi-open ranges (that is, end is the first invalid index) are much cleaner to deal with.

size_t middle = (start+end)/2; is vulnerable to arithmetic overflow. Consider
  size_t middle = start + (end - start)/2;


Avoid VLAs. They are vulnerable to stack overflow, and besides, they are not supported by c++ standard.

No naked loops, please. Every loop implements an algorithm, and therefore deserves a name. The before and after the actual merge in fact implement copy.

I don't see anything resembling c++.  The entire code is plain old c.