Transforming elements of std containers from other std containers
Some things to note :


As you said there are std::min(m,n)/2; rings. 
the ring R(r, p) has 2*(r+p-2) elements.
the top left of the ring has always coordinates (x,x). This is visible in your code :) j = ring_i; v_ring.push_back(mat[ring_i][j]);
Your code is using a relatively efficient amount of memory for the in-place transformation, and running in O(m*n) time, which is the best possible.
A performance improvement will be to use a single vector for the data in the matrix such that m(x,y) = m.data[x * cols + y] 


Can we do it differently ?

Given the local coordinates system where (0, 0) is the top left of the ring



A coordinate (i,j), 0&lt;=i&lt;w, 0&lt;=j&lt;h, j == 0 || i == 0 on a side of a ring, can be moved by one position using the function

std::pair&lt;int, int&gt;
rotated_local_next(int i, int j, int w, int h)
{
   if(j == 0) //top , special case top-right
   {
        return i == w-1 ? std::make_pair(i, ++j) : std::make_pair(++i, j);
   }
   if(i == w-1) //right, special case bottom-right
   {
        return j == h-1 ? std::make_pair(--i, j) : std::make_pair(i, ++j);
   }
   if(j == h-1) //bottom, special case bottom-left
   {
        return i == 0 ? std::make_pair(i, --j) : std::make_pair(--i, j);
   }
   if(i == 0) //left, special case top-left
   {
        return j == 0 ? std::make_pair(++i, j): std::make_pair(i, --j);
   }

   // cannot happen
   throw std::exception{};
}


You can now implement in matrix coordinates

std::pair&lt;int, int&gt;
rotated_matrix_next(int x, int y, int ring_i, int height, int width )
{
     auto local_point = rotated_local_next(x-ring_i, y-ring_i, width-2*(ring_i), height-2*(ring_i));
     return std::make_pair(local_point.first + ring_i, local_point.second + ring_i);
}


All you need to do now is to implement iterators satisfying MoveAssignable and MoveConstructible. The beginning of your ring is the element at positing ring, ring, which is also the end of the ring.

struct RingView
{
    //ring start at 0
   RingView(std::vector&lt;std::vector&lt;int&gt;&gt;&amp; mat, int ringnumber)
   : matrix(mat)
   , rows(mat.size())
   , cols(mat[0].size())
   , ring(ringnumber)
   {}

   std::vector&lt;std::vector&lt;int&gt;&gt;&amp; matrix;
   int rows;
   int cols;
   int ring;

    class ring_iterator : public std::iterator&lt;std::forward_iterator_tag, int&gt;
    {
    public:

        ring_iterator(RingView* v = nullptr, int xpos = 0, int ypos = 0)
        : view(v)
        , x(xpos)
        , y(ypos)
        {}

        ring_iterator(const ring_iterator&amp; other) = default;
        ring_iterator(ring_iterator&amp;&amp; other) = default;
        ring_iterator&amp; operator =(ring_iterator const&amp;) = default;
        ring_iterator&amp; operator=(ring_iterator&amp;&amp;) = default;
        virtual ~ring_iterator() = default;

        RingView* view;
        int x;
        int y;

        // Forward
        ring_iterator&amp; operator++() {
            auto posnext = rotated_matrix_next(x, y, view-&gt;ring, view-&gt;cols, view-&gt;rows);

            x = posnext.first;
            y = posnext.second;
            return *this;
        }

        ring_iterator operator++(int) {
            auto posnext = rotated_matrix_next(x, y, view-&gt;ring, view-&gt;cols, view-&gt;rows);
            ring_iterator next_iter = *this;
            next_iter.x = posnext.first;
            next_iter.y = posnext.second;
            return next_iter;
        }

        bool operator==(const ring_iterator&amp; other) const {
            if(view == nullptr ){
                return other.view == nullptr;
            }
            return view-&gt;ring == other.view-&gt;ring &amp;&amp; x == other.x &amp;&amp; y == other.y;
        }

        bool operator!=(const ring_iterator&amp; other) const {
            return !(*this == other);
        }

        // usually required
        int&amp; operator*() {
            return view-&gt;matrix[x][y];
        }
        int* operator-&gt;() {
            return &amp;(view-&gt;matrix[x][y]);
        }
    };

    ring_iterator begin()
    {
        return ring_iterator(this, ring, ring);
    }

    ring_iterator end()
    {
        return begin();
    }

    int size() const
    {
        return 2*(rows+cols -2) - 4 * ring;
    }
};


Now you can rotate the matrix using the view. To be able to rotate in both directions (positive for clockwise, negative counter-clockwise) we use

r' = (r % view.size() + view.size()) % view.size();

void rotate_mat (Matrix &amp;mat, int r){
    auto n_rings = std::min(mat.size(),mat[0].size())/2; // Number of rings
    for(auto ring_i=0; ring_i&lt;n_rings; ++ring_i){
        RingView view(mat,ring_i);
        int r_modulo = (r % view.size() + view.size()) % view.size();
        auto next_location = view.begin();
        std::advance(next_location, r_modulo);
        std::rotate(view.begin(),next_location,view.end());
    }
}


Full code : https://ideone.com/I3vg5v