add rec to audio and base modern concepts

This commit is contained in:
2025-09-02 12:28:56 +03:00
parent 7832b2095b
commit e08f299d5c
10 changed files with 633 additions and 91 deletions

View File

@@ -2,12 +2,15 @@
* Это очень нужная коллекция фрагментов рабочего кода для возможного упрощения жизни.*
Автор не претендует на чистоту, реализации и верность исполнения, поэтому, если вы думаете, что можете сделать это по-другому, то пожалуйста, сделайте это.
Автор не претендует на чистоту, реализации и верность исполнения, поэтому если вы думаете, что можете сделать это по-другому, то пожалуйста, сделайте это.
Пожалуйста, смотрите пример реализации в /bin/main.cpp и tests/...
Пожалуйста, смотрите пример реализации в /bin/имя_желаемого.cpp и tests/...
Что тут:
- audio - набор методов по работе со звуком: воспроизведение, запись, работа с массивом звуков и т.п.
- concepts - набор разнообразных реализаций концептов
- iterators - набор разнообразных реализаций итераторов
- log - реализация лдогирования
- patterns - набор различных паттернов проектирования

41
bin/audio.cpp Normal file
View File

@@ -0,0 +1,41 @@
#include "hack/audio/generate.hpp"
#include "hack/audio/play.hpp"
#include "hack/audio/save.hpp"
#include "hack/audio/record.hpp"
#include "hack/logger/logger.hpp"
auto main(int argc, char *argv[]) -> int
{
hack::warn()("Пример работы: audio");
int sample_rate = 44100;
// созданиен массива звуков
std::vector<double> frequencies = { 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94 };
std::vector<double> melody;
for (double freq : frequencies)
{
auto note = hack::audio::generate::sine(freq, 0.3, sample_rate);
melody.insert(melody.end(), note.begin(), note.end());
// Добавляем небольшую паузу между нотами. Т.е. вставляем кол-во с нулевым значением.
melody.insert(melody.end(), sample_rate * 0.05, 0.0);
}
hack::log()("Воспроизведение последовательности нот из массива");
hack::audio::play(melody, sample_rate);
hack::log()("Запись последовательности нот масива в файл");
std::string file = "/mnt/raid/projects/hack/hack/bin/test/note.wav";
hack::audio::save(file, melody, sample_rate);
hack::log()("Воспроизведение последовательности нот из файла");
hack::audio::play(file);
hack::log()("Запись аудио с микрофона");
std::vector<double> recorded_data;
hack::audio::record(recorded_data, sample_rate, 1, 5.0);
hack::log()("Воспроизведение аудио с микрофона");
hack::audio::play(recorded_data, sample_rate, 1);
}

View File

@@ -1,10 +1,6 @@
#include <vector>
#include <forward_list>
#include "hack/audio/generate.hpp"
#include "hack/audio/play.hpp"
#include "hack/audio/save.hpp"
#include "hack/logger/logger.hpp"
#include "hack/range/sort.hpp"
@@ -18,28 +14,16 @@
auto main(int argc, char *argv[]) -> int
{
// HERE
// concepts
{
int sample_rate = 44100;
// Воспроизведение последовательности нот
hack::warn()("Пример использования: audio");
std::vector<double> frequencies = { 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94 };
std::vector<double> melody;
for (double freq : frequencies)
{
auto note = hack::audio::generate::sine(freq, 0.3, sample_rate);
melody.insert(melody.end(), note.begin(), note.end());
// Добавляем небольшую паузу между нотами. Т.е. вставляем кол-во с нулевым значением.
melody.insert(melody.end(), sample_rate * 0.05, 0.0);
}
hack::log()("Воспроизведение последовательности нот...");
hack::audio::play(melody, sample_rate);
hack::log()("Запись последовательности нот в файл");
hack::audio::save("/mnt/raid/projects/hack/hack/bin/test/note.wav", melody, sample_rate);
}
// patterns::ring_buffer
{
hack::patterns::ring_buffer<int> rb;

View File

@@ -1,6 +1,6 @@
executable(
meson.project_name(),
'main.cpp',
'audio.cpp',
dependencies : deps,
cpp_args: args,
include_directories : inc

View File

@@ -3,10 +3,10 @@
#include <vector>
#include <math.h>
// Генерация разнообразных звуковых волн
// Генерация разнообразных звуковых волн.
namespace hack::audio::generate
{
// Генерация синусоидального сигнала
// Генерация синусоидального сигнала.
inline std::vector<double> sine(double frequency, double duration, int sample_rate)
{
std::vector<double> samples;
@@ -22,7 +22,7 @@ namespace hack::audio::generate
return samples;
}
// Генерация прямоугольного сигнала
// Генерация прямоугольного сигнала.
inline std::vector<double> square_wave(double frequency, double duration, int sample_rate)
{
std::vector<double> samples;
@@ -38,7 +38,7 @@ namespace hack::audio::generate
return samples;
}
// Генерация белого шума
// Генерация белого шума.
inline std::vector<double> white_noise(double duration, int sample_rate)
{
std::vector<double> samples;
@@ -47,7 +47,7 @@ namespace hack::audio::generate
for (int i = 0; i < total_samples; ++i)
{
double sample = (static_cast<double>(rand()) / RAND_MAX) * 2.0 - 1.0;
samples.push_back(sample * 0.3); // Уменьшаем громкость
samples.push_back(sample * 0.3); // Уменьшаем громкость.
}
return samples;

3
src/hack/audio/info.md Normal file
View File

@@ -0,0 +1,3 @@
Вся работа с аудио на данный момент крутиться во круг wav.
Т.е. что-то типа - воспроизвести/записать в mp3 или что-то подобное требует своей реализации, которой
буду заниматься по мерее её необходимости !!!

View File

@@ -0,0 +1,55 @@
#pragma once
#include <portaudio.h>
#include <mutex>
namespace hack::audio
{
// Данный класс, является реализацией безопастного отключения аудио-устройства
// вашего компа и убирает лишнюю писанину типа Pa_Terminate() и естественного отслеживания таких вызовов.
// Также он типа потоко-безопасен.
class pa_manager
{
public:
static pa_manager& instance()
{
static pa_manager pam;
return pam;
}
bool initialize()
{
std::lock_guard<std::mutex> lock { m_mutex };
if (!m_initialized)
{
PaError err = Pa_Initialize();
if (err != paNoError) return false;
m_initialized = true;
}
return true;
}
void terminate()
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_initialized)
{
Pa_Terminate();
m_initialized = false;
}
}
~pa_manager()
{
terminate();
}
private:
pa_manager() : m_initialized { false } {}
pa_manager(const pa_manager&) = delete;
pa_manager& operator=(const pa_manager&) = delete;
bool m_initialized;
std::mutex m_mutex;
};
}

View File

@@ -1,76 +1,130 @@
#pragma once
#include <vector>
#include <portaudio.h>
#include <sndfile.hh>
#include "hack/audio/manager.hpp"
#include "hack/logger/logger.hpp"
namespace hack::audio
{
namespace
{
const int FRAMES_PER_BUFFER = 256;
// Типичные значения, используемые в индустрии:
// FRAMES_PER_BUFFER = 256; Более требовательный к CPU
// FRAMES_PER_BUFFER = 512; Оптимальный баланс
// FRAMES_PER_BUFFER = 1024; Большая задержка, но минимальная нагрузка
const int FRAMES_PER_BUFFER = 512;
// callback - вызывается, когда нужны новые аудио-данные
static int callback(const void*, void* output_buffer, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* data)
struct array_data
{
auto& samples = *static_cast<std::vector<double>*>(data);
static size_t pos = 0;
std::vector<double> samples;
size_t position = 0;
};
struct file_data
{
SndfileHandle file;
std::vector<double> buffer;
};
// Колбэк для воспроизведения из памяти.
static int array_callback(const void*, void* output_buffer, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* data)
{
auto* audio_data = static_cast<array_data*>(data);
auto* out = static_cast<float*>(output_buffer);
for (unsigned long i = 0; i < frames; ++i)
{
// передаем даннеы если есть, иначе - тишина.
if (pos < samples.size()) out[i] = static_cast<float>(samples[pos++]);
else out[i] = 0.0f;
if (audio_data->position < audio_data->samples.size())
out[i] = static_cast<float>(audio_data->samples[audio_data->position++]);
else
out[i] = 0.0f;
}
return (pos >= samples.size()) ? paComplete : paContinue;
return (audio_data->position >= audio_data->samples.size()) ? paComplete : paContinue;
}
// Колбэк для воспроизведения из файла.
static int file_callback(const void*, void* output_buffer, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* data)
{
auto* fd = static_cast<file_data*>(data);
auto* out = static_cast<float*>(output_buffer);
sf_count_t frames_read = fd->file.readf(fd->buffer.data(), frames);
if (frames_read > 0)
{
for (sf_count_t i = 0; i < frames_read * fd->file.channels(); ++i)
out[i] = static_cast<float>(fd->buffer[i]);
return paContinue;
}
return paComplete;
}
// Общая функция инициализации и воспроизведения.
template<typename DataType, typename CallbackType>
void play_impl(DataType& data, CallbackType callback, int sample_rate, int channels)
{
if (!pa_manager::instance().initialize())
{
hack::error()("Failed to initialize PortAudio");
return;
}
PaStream* stream;
PaError err = Pa_OpenDefaultStream(&stream,
0, // нет входных каналов
channels, // количество каналов
paFloat32, // 32-bit floating-point
sample_rate,
FRAMES_PER_BUFFER,
callback,
&data);
if (err != paNoError)
{
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
Pa_Terminate();
return;
}
err = Pa_StartStream(stream);
if (err != paNoError)
{
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
Pa_CloseStream(stream);
return;
}
while (Pa_IsStreamActive(stream)) Pa_Sleep(100);
Pa_CloseStream(stream);
}
}
// пока только wav
inline void play(std::vector<double>& samples, int sample_rate)
inline void play(const std::vector<double>& samples, int sample_rate, int channels = 1)
{
PaError err = Pa_Initialize();
if (err != paNoError)
array_data audio_data { samples };
play_impl(audio_data, array_callback, sample_rate, channels);
}
inline void play(const std::string& filename)
{
file_data fd;
fd.file = SndfileHandle(filename);
if (fd.file.error())
{
hack::error()("PortAudio error: ", Pa_GetErrorText(err));
hack::error()("Cannot open audio file: ", filename);
return;
}
// открытие потока
PaStream* stream;
err = Pa_OpenDefaultStream(&stream,
0, // Нет входных каналов
1, // 1 выходной канал (моно)
paFloat32, // 32-bit floating-point output
sample_rate,
FRAMES_PER_BUFFER,
callback,
&samples);
int sample_rate = fd.file.samplerate();
int channels = fd.file.channels();
if (err != paNoError)
{
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
Pa_Terminate();
return;
}
// запуск потока
err = Pa_StartStream(stream);
if (err != paNoError)
{
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
Pa_CloseStream(stream);
Pa_Terminate();
return;
}
// ждём окончания воспроизведения
while (Pa_IsStreamActive(stream)) Pa_Sleep(100); // Пауза 100 мс между проверками
// очистка
Pa_CloseStream(stream);
Pa_Terminate();
// подготовка буфера
fd.buffer.resize(FRAMES_PER_BUFFER * channels);
play_impl(fd, file_callback, sample_rate, channels);
}
}

145
src/hack/audio/record.hpp Normal file
View File

@@ -0,0 +1,145 @@
#pragma once
#include <vector>
#include <atomic>
#include <csignal>
#include <sndfile.h>
#include <portaudio.h>
#include <chrono>
#include "hack/audio/manager.hpp"
#include "hack/logger/logger.hpp"
namespace hack::audio
{
namespace
{
struct record_data
{
std::vector<double>* data;
size_t max_samples;
bool complete = true;
};
std::atomic<bool> stop_recording { false };
// Колбэк для записи.
static int callback(const void* input_buffer, void*, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* user_data)
{
auto* rd = static_cast<record_data*>(user_data);
const auto* in = static_cast<const float*>(input_buffer);
if (rd->complete) return paComplete;
// добавляем samples в вектор
for (unsigned long i = 0; i < frames; ++i)
{
if (rd->data->size() < rd->max_samples)
rd->data->push_back(static_cast<double>(in[i]));
else
return paComplete;
}
return paContinue;
}
// Обработчик сигнала для остановки записи.
void signal_handler(int signal)
{
if (signal == SIGINT)
{
stop_recording = true;
hack::log()("Stopping recording...");
}
}
}
inline void record(std::vector<double>& data, int sample_rate, int channels, double duration_seconds)
{
data.clear();
stop_recording = false;
// устанавливаем обработчик сигнала
// нужен для обработки сигнала Ctrl+C (прерывание с клавиатуры)
std::signal(SIGINT, signal_handler);
if (!pa_manager::instance().initialize())
{
hack::error()("Failed to initialize PortAudio");
return;
}
// получаем информацию о default input device
PaDeviceIndex input_device = Pa_GetDefaultInputDevice();
if (input_device == paNoDevice)
{
hack::error()("No input device found!");
return;
}
const PaDeviceInfo* device_info = Pa_GetDeviceInfo(input_device);
hack::log()("Using input device: ", device_info->name);
hack::log()("Sample rate: ", sample_rate);
hack::log()("Channels: ", channels);
hack::log()("Duration: ", duration_seconds, " sec.");
size_t max_samples = static_cast<size_t>(sample_rate * duration_seconds);
data.reserve(max_samples);
record_data rd;
rd.data = &data;
rd.max_samples = max_samples;
rd.complete = false;
PaStreamParameters input_parameters;
input_parameters.device = input_device;
input_parameters.channelCount = channels;
input_parameters.sampleFormat = paFloat32;
input_parameters.suggestedLatency = device_info->defaultLowInputLatency;
input_parameters.hostApiSpecificStreamInfo = nullptr;
PaStream* stream;
PaError err = Pa_OpenStream(&stream,
&input_parameters,
nullptr, // No output
sample_rate,
512,
paClipOff,
callback,
&rd);
if (err != paNoError)
{
hack::error()("PortAudio error: ", Pa_GetErrorText(err));
return;
}
err = Pa_StartStream(stream);
if (err != paNoError)
{
Pa_CloseStream(stream);
hack::error()("PortAudio error: ", Pa_GetErrorText(err));
return;
}
hack::warn()("Recording... Press Ctrl+C to stop early");
// основной цикл записи
auto start_time = std::chrono::steady_clock::now();
while (Pa_IsStreamActive(stream) && !rd.complete && !stop_recording)
{
auto current_time = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(current_time - start_time).count();
if (elapsed >= duration_seconds) break;
Pa_Sleep(100);
}
// завершение записи
err = Pa_StopStream(stream);
if (err != paNoError)
hack::error()("PortAudio error stopping stream: ", Pa_GetErrorText(err));
Pa_CloseStream(stream);
hack::log()("Recording finished. Captured ", data.size(), " samples (", static_cast<double>(data.size()) / sample_rate, " seconds)");
}
}

View File

@@ -8,9 +8,18 @@
#include <unordered_map>
#include <forward_list>
#include <string_view>
#include <type_traits>
#include <string>
#include <deque>
#include <array>
#include <stack>
#include <queue>
#include <tuple>
#include <utility>
#include <concepts>
// HERE
// Это старая версия. Перед удалением нужнопроверить ее работу по сравнению с работой новой версии
namespace hack::concepts
{
template<typename T>
@@ -40,13 +49,261 @@ namespace hack::concepts
template<typename T>
concept not_defined = !std::enable_if_t<!(std::integral<T> ||
is_sequence_container<T> ||
is_map<T> ||
is_tuple<T> ||
is_set<T> ||
is_unordered_set<T> ||
is_forward_list<T> ||
std::is_array<T>() ||
is_string<T>), bool>() == true;
concept not_defined = !std::enable_if_t<!(std::integral<T> || is_sequence_container<T> || is_map<T> || is_tuple<T> || is_set<T> || is_unordered_set<T> ||
is_forward_list<T> || std::is_array<T>() || is_string<T>), bool>() == true;
}
namespace hack::concepts::modern
{
// Базовые концепты для обнаружения характеристик типов.
// Эти концепты являются строительными блоками для более сложных проверок.
// @brief Проверяет, имеет ли тип вложенный тип value_type
// @details Используется для обнаружения контейнеров, которые хранят элементы
// Подходит для vector, list, set и других контейнеров STL
template<typename T>
concept has_value_type = requires { typename T::value_type; };
// @brief Проверяет, имеет ли тип вложенный тип key_type
// @details Характерно для ассоциативных контейнеров (map, set)
// Помогает отличать контейнеры по ключам от контейнеров по значениям
template<typename T>
concept has_key_type = requires { typename T::key_type; };
// @brief Проверяет, имеет ли тип вложенный тип mapped_type
// @details Специфично для map-подобных контейнеров, которые хранят пары ключ-значение
// Отличает map от set (у set нет mapped_type)
template<typename T>
concept has_mapped_type = requires { typename T::mapped_type; };
// @brief Проверяет, имеет ли тип методы begin() и end()
// @details Основной концепт для обнаружения итерируемых типов
// Работает с любым типом, который можно использовать в range-based for loops
template<typename T>
concept has_iterator = requires(T t) { t.begin(); t.end(); };
// @brief Проверяет, имеет ли тип метод size()
// @details Обнаруживает контейнеры, которые знают свой размер
// Важно для алгоритмов, требующих знания размера контейнера
template<typename T>
concept has_size = requires(T t) { t.size(); };
// @brief Проверяет, имеет ли тип вложенный тип key_compare
// @details Специфично для упорядоченных ассоциативных контейнеров (map, set)
// Помогает определить, используется ли компаратор для упорядочивания
template<typename T>
concept has_key_compare = requires { typename T::key_compare; };
// @brief Проверяет, имеет ли тип вложенный тип allocator_type
// @details Обнаруживает контейнеры, которые используют аллокаторы
// Полезно для кастомного управления памятью и продвинутых оптимизаций
template<typename T>
concept has_allocator_type = requires { typename T::allocator_type; };
// Строковые типы
// @brief Проверяет, является ли тип строковым
// @details Универсальная проверка для всех видов строк:
// - std::string и его варианты (wstring, u16string, u32string)
// - Строковые литералы (char*, const char*)
// - std::string_view
// Конвертация в string_view охватывает большинство строкоподобных типов
template<typename T>
concept is_string = std::convertible_to<T, std::string_view> || std::same_as<T, std::string> || std::same_as<T, std::wstring> || std::same_as<T, std::u16string> || std::same_as<T, std::u32string>;
// Последовательные контейнеры
// @brief Проверяет, является ли тип последовательным контейнером STL
// @details Обнаруживает стандартные последовательные контейнеры:
// - std::vector - динамический массив
// - std::list - двусвязный список
// - std::deque - двусторонняя очередь
// - std::forward_list - односвязный список
// Проверяет наличие итераторов и value_type для универсальности
template<typename T>
concept is_sequence_container = has_iterator<T> &&
has_value_type<T> &&
(std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type>> ||
std::same_as<T, std::list<typename T::value_type, typename T::allocator_type>> ||
std::same_as<T, std::deque<typename T::value_type, typename T::allocator_type>> ||
std::same_as<T, std::forward_list<typename T::value_type, typename T::allocator_type>>);
// Контейнеры с произвольным доступом
// @brief Проверяет, поддерживает ли контейнер произвольный доступ
// @details Обнаруживает контейнеры с оператором [] для быстрого доступа:
// - std::vector - O(1) доступ по индексу
// - std::deque - O(1) доступ по индексу
// - std::array - фиксированный массив с быстрым доступом
// Важно для алгоритмов, требующих частого доступа к элементам по индексу
template<typename T>
concept is_random_access_container = is_sequence_container<T> && requires(T t, std::size_t idx) { t[idx]; };
// Адаптеры контейнеров
// @brief Проверяет, является ли тип адаптером контейнера
// @details Обнаруживает типы-обертки над другими контейнерами:
// - std::stack - LIFO стек
// - std::queue - FIFO очередь
// - std::priority_queue - очередь с приоритетом
// Эти типы предоставляют ограниченный интерфейс поверх базовых контейнеров
template<typename T>
concept is_container_adapter = std::same_as<T, std::stack<typename T::value_type, typename T::container_type>> ||
std::same_as<T, std::queue<typename T::value_type, typename T::container_type>> ||
std::same_as<T, std::priority_queue<typename T::value_type, typename T::container_type>>;
// Ассоциативные контейнеры (универсальная проверка)
// @brief Проверяет, является ли тип упорядоченным ассоциативным контейнером
// @details Обнаруживает контейнеры, хранящие элементы в отсортированном порядке:
// - std::map - словарь с уникальными ключами
// - std::multimap - словарь с возможностью дубликатов ключей
// - std::set - множество уникальных элементов
// - std::multiset - множество с возможностью дубликатов
// Использует key_compare для упорядочивания (обычно std::less)
template<typename T>
concept is_associative_container = has_iterator<T> &&
has_key_type<T> &&
(has_mapped_type<T> || !has_mapped_type<T>) && // Для map и set
(std::same_as<T, std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>> ||
std::same_as<T, std::multimap<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>> ||
std::same_as<T, std::set<typename T::key_type, typename T::key_compare, typename T::allocator_type>> ||
std::same_as<T, std::multiset<typename T::key_type, typename T::key_compare, typename T::allocator_type>>);
// Неупорядоченные ассоциативные контейнеры
// @brief Проверяет, является ли тип неупорядоченным ассоциативным контейнером
// @details Обнаруживает контейнеры, использующие хеширование:
// - std::unordered_map - хеш-таблица с уникальными ключами
// - std::unordered_multimap - хеш-таблица с дубликатами ключей
// - std::unordered_set - хеш-множество уникальных элементов
// - std::unordered_multiset - хеш-множество с дубликатами
// Используют хеш-функции и сравнение на равенство вместо упорядочивания
template<typename T>
concept is_unordered_associative_container = has_iterator<T> && has_key_type<T> && (has_mapped_type<T> || !has_mapped_type<T>) &&
(std::same_as<T, std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>> ||
std::same_as<T, std::unordered_multimap<typename T::key_type, typename T::mapped_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>> ||
std::same_as<T, std::unordered_set<typename T::key_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>> ||
std::same_as<T, std::unordered_multiset<typename T::key_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>>);
// Кортежи и пары
// @brief Проверяет, является ли тип кортежоподобным
// @details Обнаруживает типы, которые можно разбирать через tuple_size и tuple_element:
// - std::tuple - гетерогенный кортеж
// - std::pair - пара из двух элементов
// - std::array - также удовлетворяет этому концепту
// - Пользовательские кортежи с специализацией tuple_size
// Исключает строки и контейнеры для избежания ложных срабатываний
template<typename T>
concept is_tuple_like = requires(T t) {
std::tuple_size<T>::value;
requires std::tuple_size<T>::value >= 0;
} && !is_string<T> && !is_sequence_container<T>; // Исключаем конфликты
// Массивы
// @brief Проверяет, является ли тип встроенным массивом
// @details Обнаруживает C-style массивы: int[10], char[5], etc.
// Проверяет, что размер массива больше 0 (не incomplete array)
template<typename T>
concept is_fixed_array = std::is_array_v<T> && std::extent_v<T> > 0;
// @brief Проверяет, является ли тип std::array
// @details Обнаруживает std::array с конкретным размером
// Полезно для алгоритмов, которые требуют знания размера на этапе компиляции
template<typename T, std::size_t N>
concept is_std_array = std::same_as<T, std::array<typename T::value_type, N>>;
// Универсальные концепты для категоризации
// @brief Проверяет, является ли тип любым контейнером
// @details Всеобъемлющий концепт для обнаружения контейнеров любого типа:
// - Последовательные контейнеры
// - Ассоциативные контейнеры
// - Адаптеры контейнеров
// - Массивы (C-style и std::array)
// Основной концепт для обобщенных алгоритмов работы с контейнерами
template<typename T>
concept is_any_container = is_sequence_container<T> ||
is_associative_container<T> ||
is_unordered_associative_container<T> ||
is_container_adapter<T> ||
is_fixed_array<T> ||
is_std_array<T, std::tuple_size_v<T>>;
// @brief Проверяет, является ли тип итерируемым
// @details Обнаруживает любые типы, по которым можно итерироваться:
// - Контейнеры STL с begin()/end()
// - Массивы (работают с std::begin/std::end)
// - Кортежи (хотя итерирование по ним особое)
// Самый общий концепт для range-based for и алгоритмов
template<typename T>
concept is_iterable = has_iterator<T> || is_fixed_array<T> || is_tuple_like<T>;
// @brief Проверяет, имеет ли тип размер
// @details Обнаруживает типы, у которых можно узнать размер:
// - Контейнеры с методом size()
// - Массивы с известным размером
// - Кортежи с известным количеством элементов
// Важно для алгоритмов, требующих предварительного знания размера
template<typename T>
concept is_sized = has_size<T> || is_fixed_array<T> || is_tuple_like<T>;
// Концепт для "неподдерживаемых" типов
// @brief Проверяет, является ли тип неподдерживаемым
// @details Обнаруживает типы, которые не входят в известные категории:
// - Пользовательские типы без ожидаемого интерфейса
// - Специфичные типы из сторонних библиотек
// - Типы, для которых нет специализированной обработки
// Используется для static_assert и генерации понятных ошибок
template<typename T>
concept not_supported = !(std::integral<T> ||
std::floating_point<T> ||
is_string<T> ||
is_any_container<T> ||
is_tuple_like<T> ||
std::is_pointer_v<T>);
// Вспомогательные концепты для метапрограммирования
// @brief Проверяет, имеет ли тип семантику ключ-значение
// @details Обнаруживает контейнеры, которые поддерживают доступ по ключу:
// - map и unordered_map (operator[])
// - set и unordered_set (хотя у set нет разделения на key/value)
// Полезно для алгоритмов работы со словарями и ассоциативными массивами
template<typename T>
concept has_key_value_semantics = is_associative_container<T> ||
is_unordered_associative_container<T> ||
requires(T t, typename T::key_type key) { t[key]; };
// @brief Проверяет, является ли тип контейнером с непрерывной памятью
// @details Обнаруживает контейнеры, элементы которых хранятся в непрерывной памяти:
// - std::vector - динамический массив
// - C-style массивы
// - std::array - статический массив
// Критически важно для низкоуровневых операций и взаимодействия с C API
template<typename T>
concept is_contiguous_container = std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type>> ||
is_fixed_array<T> ||
is_std_array<T, std::tuple_size_v<T>>;
// Концепты для алгоритмов
// @brief Проверяет, поддерживает ли контейнер emplace-операцию
// @details Обнаруживает контейнеры, которые могут создавать элементы на месте:
// - vector::emplace_back(), map::emplace(), etc.
// Позволяет избежать лишних копирований и перемещений
template<typename Container, typename Value>
concept can_emplace = requires(Container c, Value&& v) { c.emplace(std::forward<Value>(v)); };
// @brief Проверяет, поддерживает ли контейнер добавление в начало
// @details Обнаруживает контейнеры с push_front():
// - deque, list, forward_list
// Полезно для алгоритмов, работающих с очередями и стеками
template<typename Container, typename Value>
concept can_push_front = requires(Container c, Value&& v) { c.push_front(std::forward<Value>(v)); };
// @brief Проверяет, поддерживает ли контейнер добавление в конец
// @details Обнаруживает контейнеры с push_back():
// - vector, deque, list
// Важно для алгоритмов, которые строят контейнеры последовательно
template<typename Container, typename Value>
concept can_push_back = requires(Container c, Value&& v) { c.push_back(std::forward<Value>(v)); };
// @brief Проверяет, поддерживает ли контейнер поиск по ключу
// @details Обнаруживает контейнеры с методом find():
// - map, set, unordered_map, unordered_set
// Ключевой концепт для алгоритмов поиска и проверки существования элементов
template<typename Container, typename Key>
concept can_find = requires(Container c, Key&& key) { c.find(std::forward<Key>(key)); };
}