mirror of
https://github.com/LMMS/lmms.git
synced 2025-12-23 22:58:33 -05:00
* Add `ArrayVector` class template and tests * Fix counting of failed test suites * Support detuning and panning with Sf2 Player * Restrict panning to supported FluidSynth versions * Fix data array cast type * Fix tests for Qt<5.10 and correct mistaken test * DIsplay warning for FluidSynth < 2 * Remove unnecessary clamp * Update include guard name
389 lines
12 KiB
C++
389 lines
12 KiB
C++
/*
|
|
* ArrayVector.h
|
|
*
|
|
* Copyright (c) 2023 Dominic Clark
|
|
*
|
|
* This file is part of LMMS - https://lmms.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program (see COPYING); if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA.
|
|
*
|
|
*/
|
|
|
|
#ifndef LMMS_ARRAY_VECTOR_H
|
|
#define LMMS_ARRAY_VECTOR_H
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <new>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
#include <type_traits>
|
|
|
|
namespace lmms {
|
|
|
|
namespace detail {
|
|
|
|
template<typename T, typename = void>
|
|
constexpr bool is_input_iterator_v = false;
|
|
|
|
template<typename T>
|
|
constexpr bool is_input_iterator_v<T, std::void_t<typename std::iterator_traits<T>::iterator_category>> =
|
|
std::is_convertible_v<typename std::iterator_traits<T>::iterator_category, std::input_iterator_tag>;
|
|
|
|
} // namespace detail
|
|
|
|
/**
|
|
* A container that stores up to a maximum of `N` elements of type `T` directly
|
|
* within itself, rather than separately on the heap. Useful when a dynamically
|
|
* resizeable container is needed for use in real-time code. Can be thought of
|
|
* as a hybrid between `std::array` and `std::vector`. The interface follows
|
|
* that of `std::vector` - see standard C++ documentation.
|
|
*/
|
|
template<typename T, std::size_t N>
|
|
class ArrayVector
|
|
{
|
|
public:
|
|
using size_type = std::size_t;
|
|
using difference_type = std::ptrdiff_t;
|
|
using value_type = T;
|
|
using reference = T&;
|
|
using const_reference = const T&;
|
|
using pointer = T*;
|
|
using const_pointer = const T*;
|
|
using iterator = pointer;
|
|
using const_iterator = const_pointer;
|
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
|
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
|
|
|
ArrayVector() = default;
|
|
|
|
ArrayVector(const ArrayVector& other) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
|
m_size{other.m_size}
|
|
{
|
|
std::uninitialized_copy(other.begin(), other.end(), begin());
|
|
}
|
|
|
|
ArrayVector(ArrayVector&& other) noexcept(std::is_nothrow_move_constructible_v<T>) :
|
|
m_size{other.m_size}
|
|
{
|
|
std::uninitialized_move(other.begin(), other.end(), begin());
|
|
other.clear();
|
|
}
|
|
|
|
ArrayVector(size_type count, const T& value) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
|
m_size{count}
|
|
{
|
|
assert(count <= N);
|
|
std::uninitialized_fill_n(begin(), count, value);
|
|
}
|
|
|
|
explicit ArrayVector(size_type count) noexcept(std::is_nothrow_default_constructible_v<T>) :
|
|
m_size{count}
|
|
{
|
|
assert(count <= N);
|
|
std::uninitialized_value_construct_n(begin(), count);
|
|
}
|
|
|
|
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
|
ArrayVector(It first, It last)
|
|
{
|
|
// Can't check the size first as the iterator may not be multipass
|
|
const auto end = std::uninitialized_copy(first, last, begin());
|
|
m_size = end - begin();
|
|
assert(m_size <= N);
|
|
}
|
|
|
|
ArrayVector(std::initializer_list<T> il) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
|
m_size{il.size()}
|
|
{
|
|
assert(il.size() <= N);
|
|
std::uninitialized_copy(il.begin(), il.end(), begin());
|
|
}
|
|
|
|
~ArrayVector() { std::destroy(begin(), end()); }
|
|
|
|
ArrayVector& operator=(const ArrayVector& other)
|
|
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
|
{
|
|
if (this != &other) {
|
|
const auto toAssign = std::min(other.size(), size());
|
|
const auto assignedFromEnd = other.begin() + toAssign;
|
|
const auto assignedToEnd = std::copy(other.begin(), other.begin() + toAssign, begin());
|
|
std::destroy(assignedToEnd, end());
|
|
std::uninitialized_copy(assignedFromEnd, other.end(), end());
|
|
m_size = other.size();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
ArrayVector& operator=(ArrayVector&& other)
|
|
noexcept(std::is_nothrow_move_assignable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
|
{
|
|
if (this != &other) {
|
|
const auto toAssign = std::min(other.size(), size());
|
|
const auto assignedFromEnd = other.begin() + toAssign;
|
|
const auto assignedToEnd = std::move(other.begin(), other.begin() + toAssign, begin());
|
|
std::destroy(assignedToEnd, end());
|
|
std::uninitialized_move(assignedFromEnd, other.end(), end());
|
|
m_size = other.size();
|
|
other.clear();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
ArrayVector& operator=(std::initializer_list<T> il)
|
|
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
|
{
|
|
assert(il.size() <= N);
|
|
const auto toAssign = std::min(il.size(), size());
|
|
const auto assignedFromEnd = il.begin() + toAssign;
|
|
const auto assignedToEnd = std::copy(il.begin(), assignedFromEnd, begin());
|
|
std::destroy(assignedToEnd, end());
|
|
std::uninitialized_copy(assignedFromEnd, il.end(), end());
|
|
m_size = il.size();
|
|
return *this;
|
|
}
|
|
|
|
void assign(size_type count, const T& value)
|
|
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
|
{
|
|
assert(count <= N);
|
|
const auto temp = value;
|
|
const auto toAssign = std::min(count, size());
|
|
const auto toConstruct = count - toAssign;
|
|
const auto assignedToEnd = std::fill_n(begin(), toAssign, temp);
|
|
std::destroy(assignedToEnd, end());
|
|
std::uninitialized_fill_n(assignedToEnd, toConstruct, temp);
|
|
m_size = count;
|
|
}
|
|
|
|
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
|
void assign(It first, It last)
|
|
{
|
|
// Can't check the size first as the iterator may not be multipass
|
|
auto pos = begin();
|
|
for (; first != last && pos != end(); ++pos, ++first) {
|
|
*pos = *first;
|
|
}
|
|
std::destroy(pos, end());
|
|
pos = std::uninitialized_copy(first, last, pos);
|
|
m_size = pos - begin();
|
|
assert(m_size <= N);
|
|
}
|
|
|
|
reference at(size_type index)
|
|
{
|
|
if (index >= m_size) { throw std::out_of_range{"index out of range"}; }
|
|
return data()[index];
|
|
}
|
|
|
|
const_reference at(size_type index) const
|
|
{
|
|
if (index >= m_size) { throw std::out_of_range{"index out of range"}; }
|
|
return data()[index];
|
|
}
|
|
|
|
reference operator[](size_type index) noexcept
|
|
{
|
|
assert(index < m_size);
|
|
return data()[index];
|
|
}
|
|
|
|
const_reference operator[](size_type index) const noexcept
|
|
{
|
|
assert(index < m_size);
|
|
return data()[index];
|
|
}
|
|
|
|
reference front() noexcept { return operator[](0); }
|
|
const_reference front() const noexcept { return operator[](0); }
|
|
|
|
reference back() noexcept { return operator[](m_size - 1); }
|
|
const_reference back() const noexcept { return operator[](m_size - 1); }
|
|
|
|
pointer data() noexcept { return *std::launder(reinterpret_cast<T(*)[N]>(m_data)); }
|
|
const_pointer data() const noexcept { return *std::launder(reinterpret_cast<const T(*)[N]>(m_data)); }
|
|
|
|
iterator begin() noexcept { return data(); }
|
|
const_iterator begin() const noexcept { return data(); }
|
|
const_iterator cbegin() const noexcept { return data(); }
|
|
|
|
iterator end() noexcept { return data() + m_size; }
|
|
const_iterator end() const noexcept { return data() + m_size; }
|
|
const_iterator cend() const noexcept { return data() + m_size; }
|
|
|
|
reverse_iterator rbegin() noexcept { return std::reverse_iterator{end()}; }
|
|
const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator{end()}; }
|
|
const_reverse_iterator crbegin() const noexcept { return std::reverse_iterator{cend()}; }
|
|
|
|
reverse_iterator rend() noexcept { return std::reverse_iterator{begin()}; }
|
|
const_reverse_iterator rend() const noexcept { return std::reverse_iterator{begin()}; }
|
|
const_reverse_iterator crend() const noexcept { return std::reverse_iterator{cbegin()}; }
|
|
|
|
bool empty() const noexcept { return m_size == 0; }
|
|
bool full() const noexcept { return m_size == N; }
|
|
size_type size() const noexcept { return m_size; }
|
|
size_type max_size() const noexcept { return N; }
|
|
size_type capacity() const noexcept { return N; }
|
|
|
|
void clear() noexcept
|
|
{
|
|
std::destroy(begin(), end());
|
|
m_size = 0;
|
|
}
|
|
|
|
iterator insert(const_iterator pos, const T& value) { return emplace(pos, value); }
|
|
iterator insert(const_iterator pos, T&& value) { return emplace(pos, std::move(value)); }
|
|
|
|
iterator insert(const_iterator pos, size_type count, const T& value)
|
|
{
|
|
assert(m_size + count <= N);
|
|
assert(cbegin() <= pos && pos <= cend());
|
|
const auto mutPos = begin() + (pos - cbegin());
|
|
const auto newEnd = std::uninitialized_fill_n(end(), count, value);
|
|
std::rotate(mutPos, end(), newEnd);
|
|
m_size += count;
|
|
return mutPos;
|
|
}
|
|
|
|
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
|
iterator insert(const_iterator pos, It first, It last)
|
|
{
|
|
// Can't check the size first as the iterator may not be multipass
|
|
assert(cbegin() <= pos && pos <= cend());
|
|
const auto mutPos = begin() + (pos - cbegin());
|
|
const auto newEnd = std::uninitialized_copy(first, last, end());
|
|
std::rotate(mutPos, end(), newEnd);
|
|
m_size = newEnd - begin();
|
|
assert(m_size <= N);
|
|
return mutPos;
|
|
}
|
|
|
|
iterator insert(const_iterator pos, std::initializer_list<T> il) { return insert(pos, il.begin(), il.end()); }
|
|
|
|
template<typename... Args>
|
|
iterator emplace(const_iterator pos, Args&&... args)
|
|
{
|
|
assert(cbegin() <= pos && pos <= cend());
|
|
const auto mutPos = begin() + (pos - cbegin());
|
|
emplace_back(std::forward<Args>(args)...);
|
|
std::rotate(mutPos, end() - 1, end());
|
|
return mutPos;
|
|
}
|
|
|
|
iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
|
|
iterator erase(const_iterator first, const_iterator last)
|
|
{
|
|
assert(cbegin() <= first && first <= last && last <= cend());
|
|
const auto mutFirst = begin() + (first - cbegin());
|
|
const auto mutLast = begin() + (last - cbegin());
|
|
const auto newEnd = std::move(mutLast, end(), mutFirst);
|
|
std::destroy(newEnd, end());
|
|
m_size = newEnd - begin();
|
|
return mutFirst;
|
|
}
|
|
|
|
void push_back(const T& value) { emplace_back(value); }
|
|
void push_back(T&& value) { emplace_back(std::move(value)); }
|
|
|
|
template<typename... Args>
|
|
reference emplace_back(Args&&... args)
|
|
{
|
|
assert(!full());
|
|
// TODO C++20: Use std::construct_at
|
|
const auto result = new(static_cast<void*>(end())) T(std::forward<Args>(args)...);
|
|
++m_size;
|
|
return *result;
|
|
}
|
|
|
|
void pop_back()
|
|
{
|
|
assert(!empty());
|
|
--m_size;
|
|
std::destroy_at(end());
|
|
}
|
|
|
|
void resize(size_type size)
|
|
{
|
|
if (size > N) { throw std::length_error{"size exceeds maximum size"}; }
|
|
if (size < m_size) {
|
|
std::destroy(begin() + size, end());
|
|
} else {
|
|
std::uninitialized_value_construct(end(), begin() + size);
|
|
}
|
|
m_size = size;
|
|
}
|
|
|
|
void resize(size_type size, const value_type& value)
|
|
{
|
|
if (size > N) { throw std::length_error{"size exceeds maximum size"}; }
|
|
if (size < m_size) {
|
|
std::destroy(begin() + size, end());
|
|
} else {
|
|
std::uninitialized_fill(end(), begin() + size, value);
|
|
}
|
|
m_size = size;
|
|
}
|
|
|
|
void swap(ArrayVector& other)
|
|
noexcept(std::is_nothrow_swappable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
|
{
|
|
using std::swap;
|
|
swap(*this, other);
|
|
}
|
|
|
|
friend void swap(ArrayVector& a, ArrayVector& b)
|
|
noexcept(std::is_nothrow_swappable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
|
{
|
|
const auto toSwap = std::min(a.size(), b.size());
|
|
const auto aSwapEnd = a.begin() + toSwap;
|
|
const auto bSwapEnd = b.begin() + toSwap;
|
|
std::swap_ranges(a.begin(), aSwapEnd, b.begin());
|
|
std::uninitialized_move(aSwapEnd, a.end(), bSwapEnd);
|
|
std::uninitialized_move(bSwapEnd, b.end(), aSwapEnd);
|
|
std::destroy(aSwapEnd, a.end());
|
|
std::destroy(bSwapEnd, b.end());
|
|
std::swap(a.m_size, b.m_size);
|
|
}
|
|
|
|
// TODO C++20: Replace with operator<=>
|
|
friend bool operator<(const ArrayVector& l, const ArrayVector& r)
|
|
{
|
|
return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());
|
|
}
|
|
friend bool operator<=(const ArrayVector& l, const ArrayVector& r) { return !(r < l); }
|
|
friend bool operator>(const ArrayVector& l, const ArrayVector& r) { return r < l; }
|
|
friend bool operator>=(const ArrayVector& l, const ArrayVector& r) { return !(l < r); }
|
|
|
|
friend bool operator==(const ArrayVector& l, const ArrayVector& r)
|
|
{
|
|
return std::equal(l.begin(), l.end(), r.begin(), r.end());
|
|
}
|
|
// TODO C++20: Remove
|
|
friend bool operator!=(const ArrayVector& l, const ArrayVector& r) { return !(l == r); }
|
|
|
|
private:
|
|
alignas(T) std::byte m_data[std::max(N * sizeof(T), std::size_t{1})]; // Intentionally a raw array
|
|
size_type m_size = 0;
|
|
};
|
|
|
|
} // namespace lmms
|
|
|
|
#endif // LMMS_ARRAY_VECTOR_H
|