Simple ring/circular buffer c++ class V2 The Original Post (v1)

I'm looking for feedback on the updated version of the code posted at the link above.

#pragma once

#include &lt;memory&gt;
#include &lt;cassert&gt;
#include &lt;type_traits&gt;

namespace datastructures {

    template&lt;class _Ty, size_t _Size&gt;
    class CircularBufferv2 {

        // uses char as type to prevent default _Ty initialization.
        alignas(alignof(_Ty)) char buffer[_Size * sizeof(_Ty)];
        size_t head;
        size_t tail;
        bool isFull;

    public:
        constexpr CircularBufferv2() noexcept :
            buffer{0},
            head{0},
            tail{0},
            isFull{false} {
        }

        void push(const _Ty&amp; item) noexcept {
            assert(!isFull &amp;&amp; "Attempting to push item into full buffer!");

            new (&amp;buffer[head * sizeof(_Ty)]) _Ty(std::move(item));

            head = ++head % _Size;
            isFull = head == tail;
        }

        _Ty pop() noexcept {
            assert(!is_empty() &amp;&amp; "Attempting to pop item from empty buffer!");

            auto location = reinterpret_cast&lt;_Ty*&gt;(&amp;buffer[tail * sizeof(_Ty)]);
            auto result = std::move(*location);
            std::destroy_at(location);

            tail = ++tail % _Size;
            isFull = false;

            return result;
        }

        _NODISCARD constexpr _Ty&amp; peek() noexcept {
            assert(!is_empty() &amp;&amp; "Attempting to peek in empty buffer!");

            return *reinterpret_cast&lt;_Ty*&gt;(&amp;buffer[tail * sizeof(_Ty)]);
        }

        _NODISCARD constexpr const _Ty&amp; peek() const noexcept {
            assert(!is_empty() &amp;&amp; "Attempting to peek in empty buffer!");

            return *reinterpret_cast&lt;_Ty*&gt;(&amp;buffer[tail * sizeof(_Ty)]);
        }

        _NODISCARD constexpr bool is_empty() const noexcept {
            return !isFull &amp;&amp; tail == head;
        }

        _NODISCARD constexpr size_t get_capacity() const noexcept {
            return _Size;
        }

        _NODISCARD constexpr size_t get_size() const noexcept {
            if (isFull)
                return _Size;

            if (head &gt;= tail)
                return head - tail;

            return _Size + head - tail;
        }

        _NODISCARD _CONSTEXPR17 _Ty* data() noexcept {
            return buffer;
        }

        _NODISCARD _CONSTEXPR17 const _Ty* data() const noexcept {
            return buffer;
        }
    };
}


I want to take advantage of all of the new features(c++17) while also supporting older compilers(preferably all older compilers but c++11 is probably as old as i'll actually be compiling for). Any suggestions welcome. (I'm attempting to use this class as an example class to follow when constructing other classes.)

Also, the use of _CONSTEXPR17 on the data functions I was wondering why use the macro vs just a constexpr? (I based the use of macros around the std::array struct, its data function uses _CONSTEXPR17 rather than just constexpr.)
Instead of a char array you can use std::aligned_storage.

You aren't consistent with your macros you have both constexpr and _CONSTEXPR17. Pick one and stick to it.

destroy_at is standard only from C++17 onward. The equivalent syntax is location-&gt;~_Ty(); However it may be worth to put this and the placement new into a helper function to move the ugly syntax elsewhere and clarify the function.

You don't have a move variant of push or a bulk add/remove using iterators.