From e08f299d5cac911f9a7b6ffd6b066b54fae2b01e Mon Sep 17 00:00:00 2001 From: chatlanin Date: Tue, 2 Sep 2025 12:28:56 +0300 Subject: [PATCH] add rec to audio and base modern concepts --- README.md | 7 +- bin/audio.cpp | 41 +++++ bin/main.cpp | 28 +--- bin/meson.build | 2 +- src/hack/audio/generate.hpp | 10 +- src/hack/audio/info.md | 3 + src/hack/audio/manager.hpp | 55 +++++++ src/hack/audio/play.hpp | 156 +++++++++++++------ src/hack/audio/record.hpp | 145 +++++++++++++++++ src/hack/concepts/concepts.hpp | 277 +++++++++++++++++++++++++++++++-- 10 files changed, 633 insertions(+), 91 deletions(-) create mode 100644 bin/audio.cpp create mode 100644 src/hack/audio/info.md create mode 100644 src/hack/audio/manager.hpp create mode 100644 src/hack/audio/record.hpp diff --git a/README.md b/README.md index 3dc3a6c..ed34a46 100755 --- a/README.md +++ b/README.md @@ -2,12 +2,15 @@ * Это очень нужная коллекция фрагментов рабочего кода для возможного упрощения жизни.* -Автор не претендует на чистоту, реализации и верность исполнения, поэтому, если вы думаете, что можете сделать это по-другому, то пожалуйста, сделайте это. +Автор не претендует на чистоту, реализации и верность исполнения, поэтому если вы думаете, что можете сделать это по-другому, то пожалуйста, сделайте это. -Пожалуйста, смотрите пример реализации в /bin/main.cpp и tests/... +Пожалуйста, смотрите пример реализации в /bin/имя_желаемого.cpp и tests/... Что тут: +- audio - набор методов по работе со звуком: воспроизведение, запись, работа с массивом звуков и т.п. - concepts - набор разнообразных реализаций концептов + + - iterators - набор разнообразных реализаций итераторов - log - реализация лдогирования - patterns - набор различных паттернов проектирования diff --git a/bin/audio.cpp b/bin/audio.cpp new file mode 100644 index 0000000..0040303 --- /dev/null +++ b/bin/audio.cpp @@ -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 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 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 recorded_data; + hack::audio::record(recorded_data, sample_rate, 1, 5.0); + + hack::log()("Воспроизведение аудио с микрофона"); + hack::audio::play(recorded_data, sample_rate, 1); +} diff --git a/bin/main.cpp b/bin/main.cpp index ac50ea6..bf9279c 100644 --- a/bin/main.cpp +++ b/bin/main.cpp @@ -1,10 +1,6 @@ #include #include -#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 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 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 rb; diff --git a/bin/meson.build b/bin/meson.build index 016e053..0f67741 100755 --- a/bin/meson.build +++ b/bin/meson.build @@ -1,6 +1,6 @@ executable( meson.project_name(), - 'main.cpp', + 'audio.cpp', dependencies : deps, cpp_args: args, include_directories : inc diff --git a/src/hack/audio/generate.hpp b/src/hack/audio/generate.hpp index 6a65f84..c09d682 100644 --- a/src/hack/audio/generate.hpp +++ b/src/hack/audio/generate.hpp @@ -3,10 +3,10 @@ #include #include -// Генерация разнообразных звуковых волн +// Генерация разнообразных звуковых волн. namespace hack::audio::generate { - // Генерация синусоидального сигнала + // Генерация синусоидального сигнала. inline std::vector sine(double frequency, double duration, int sample_rate) { std::vector samples; @@ -22,7 +22,7 @@ namespace hack::audio::generate return samples; } - // Генерация прямоугольного сигнала + // Генерация прямоугольного сигнала. inline std::vector square_wave(double frequency, double duration, int sample_rate) { std::vector samples; @@ -38,7 +38,7 @@ namespace hack::audio::generate return samples; } - // Генерация белого шума + // Генерация белого шума. inline std::vector white_noise(double duration, int sample_rate) { std::vector samples; @@ -47,7 +47,7 @@ namespace hack::audio::generate for (int i = 0; i < total_samples; ++i) { double sample = (static_cast(rand()) / RAND_MAX) * 2.0 - 1.0; - samples.push_back(sample * 0.3); // Уменьшаем громкость + samples.push_back(sample * 0.3); // Уменьшаем громкость. } return samples; diff --git a/src/hack/audio/info.md b/src/hack/audio/info.md new file mode 100644 index 0000000..716b956 --- /dev/null +++ b/src/hack/audio/info.md @@ -0,0 +1,3 @@ +Вся работа с аудио на данный момент крутиться во круг wav. +Т.е. что-то типа - воспроизвести/записать в mp3 или что-то подобное требует своей реализации, которой +буду заниматься по мерее её необходимости !!! diff --git a/src/hack/audio/manager.hpp b/src/hack/audio/manager.hpp new file mode 100644 index 0000000..4250ae8 --- /dev/null +++ b/src/hack/audio/manager.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +namespace hack::audio +{ + // Данный класс, является реализацией безопастного отключения аудио-устройства + // вашего компа и убирает лишнюю писанину типа Pa_Terminate() и естественного отслеживания таких вызовов. + // Также он типа потоко-безопасен. + class pa_manager + { + public: + static pa_manager& instance() + { + static pa_manager pam; + return pam; + } + + bool initialize() + { + std::lock_guard 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 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; + }; +} diff --git a/src/hack/audio/play.hpp b/src/hack/audio/play.hpp index fe9a59b..7eae4f7 100644 --- a/src/hack/audio/play.hpp +++ b/src/hack/audio/play.hpp @@ -1,76 +1,130 @@ #pragma once #include -#include +#include +#include "hack/audio/manager.hpp" #include "hack/logger/logger.hpp" -namespace hack::audio +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*>(data); - static size_t pos = 0; + std::vector samples; + size_t position = 0; + }; + + struct file_data + { + SndfileHandle file; + std::vector buffer; + }; + + // Колбэк для воспроизведения из памяти. + static int array_callback(const void*, void* output_buffer, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* data) + { + auto* audio_data = static_cast(data); auto* out = static_cast(output_buffer); for (unsigned long i = 0; i < frames; ++i) { - // передаем даннеы если есть, иначе - тишина. - if (pos < samples.size()) out[i] = static_cast(samples[pos++]); - else out[i] = 0.0f; + if (audio_data->position < audio_data->samples.size()) + out[i] = static_cast(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(data); + auto* out = static_cast(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(fd->buffer[i]); + return paContinue; + } + + return paComplete; + } + + // Общая функция инициализации и воспроизведения. + template + 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& samples, int sample_rate) + inline void play(const std::vector& 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); - - 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(); + int sample_rate = fd.file.samplerate(); + int channels = fd.file.channels(); + + // подготовка буфера + fd.buffer.resize(FRAMES_PER_BUFFER * channels); + play_impl(fd, file_callback, sample_rate, channels); } } diff --git a/src/hack/audio/record.hpp b/src/hack/audio/record.hpp new file mode 100644 index 0000000..0e2ae88 --- /dev/null +++ b/src/hack/audio/record.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "hack/audio/manager.hpp" +#include "hack/logger/logger.hpp" + +namespace hack::audio +{ + namespace + { + struct record_data + { + std::vector* data; + size_t max_samples; + bool complete = true; + }; + + std::atomic stop_recording { false }; + + // Колбэк для записи. + static int callback(const void* input_buffer, void*, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* user_data) + { + auto* rd = static_cast(user_data); + const auto* in = static_cast(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(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& 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(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(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(data.size()) / sample_rate, " seconds)"); + } +} diff --git a/src/hack/concepts/concepts.hpp b/src/hack/concepts/concepts.hpp index 5367ee2..e8ad017 100755 --- a/src/hack/concepts/concepts.hpp +++ b/src/hack/concepts/concepts.hpp @@ -8,9 +8,18 @@ #include #include #include - +#include +#include +#include +#include +#include +#include +#include +#include #include +// HERE +// Это старая версия. Перед удалением нужнопроверить ее работу по сравнению с работой новой версии namespace hack::concepts { template @@ -40,13 +49,261 @@ namespace hack::concepts template - concept not_defined = !std::enable_if_t || - is_sequence_container || - is_map || - is_tuple || - is_set || - is_unordered_set || - is_forward_list || - std::is_array() || - is_string), bool>() == true; + concept not_defined = !std::enable_if_t || is_sequence_container || is_map || is_tuple || is_set || is_unordered_set || + is_forward_list || std::is_array() || is_string), bool>() == true; +} + +namespace hack::concepts::modern +{ + // Базовые концепты для обнаружения характеристик типов. + // Эти концепты являются строительными блоками для более сложных проверок. + + // @brief Проверяет, имеет ли тип вложенный тип value_type + // @details Используется для обнаружения контейнеров, которые хранят элементы + // Подходит для vector, list, set и других контейнеров STL + template + concept has_value_type = requires { typename T::value_type; }; + + // @brief Проверяет, имеет ли тип вложенный тип key_type + // @details Характерно для ассоциативных контейнеров (map, set) + // Помогает отличать контейнеры по ключам от контейнеров по значениям + template + concept has_key_type = requires { typename T::key_type; }; + + // @brief Проверяет, имеет ли тип вложенный тип mapped_type + // @details Специфично для map-подобных контейнеров, которые хранят пары ключ-значение + // Отличает map от set (у set нет mapped_type) + template + concept has_mapped_type = requires { typename T::mapped_type; }; + + // @brief Проверяет, имеет ли тип методы begin() и end() + // @details Основной концепт для обнаружения итерируемых типов + // Работает с любым типом, который можно использовать в range-based for loops + template + concept has_iterator = requires(T t) { t.begin(); t.end(); }; + + // @brief Проверяет, имеет ли тип метод size() + // @details Обнаруживает контейнеры, которые знают свой размер + // Важно для алгоритмов, требующих знания размера контейнера + template + concept has_size = requires(T t) { t.size(); }; + + // @brief Проверяет, имеет ли тип вложенный тип key_compare + // @details Специфично для упорядоченных ассоциативных контейнеров (map, set) + // Помогает определить, используется ли компаратор для упорядочивания + template + concept has_key_compare = requires { typename T::key_compare; }; + + // @brief Проверяет, имеет ли тип вложенный тип allocator_type + // @details Обнаруживает контейнеры, которые используют аллокаторы + // Полезно для кастомного управления памятью и продвинутых оптимизаций + template + 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 + concept is_string = std::convertible_to || std::same_as || std::same_as || std::same_as || std::same_as; + + // Последовательные контейнеры + // @brief Проверяет, является ли тип последовательным контейнером STL + // @details Обнаруживает стандартные последовательные контейнеры: + // - std::vector - динамический массив + // - std::list - двусвязный список + // - std::deque - двусторонняя очередь + // - std::forward_list - односвязный список + // Проверяет наличие итераторов и value_type для универсальности + template + concept is_sequence_container = has_iterator && + has_value_type && + (std::same_as> || + std::same_as> || + std::same_as> || + std::same_as>); + + // Контейнеры с произвольным доступом + // @brief Проверяет, поддерживает ли контейнер произвольный доступ + // @details Обнаруживает контейнеры с оператором [] для быстрого доступа: + // - std::vector - O(1) доступ по индексу + // - std::deque - O(1) доступ по индексу + // - std::array - фиксированный массив с быстрым доступом + // Важно для алгоритмов, требующих частого доступа к элементам по индексу + template + concept is_random_access_container = is_sequence_container && requires(T t, std::size_t idx) { t[idx]; }; + + // Адаптеры контейнеров + // @brief Проверяет, является ли тип адаптером контейнера + // @details Обнаруживает типы-обертки над другими контейнерами: + // - std::stack - LIFO стек + // - std::queue - FIFO очередь + // - std::priority_queue - очередь с приоритетом + // Эти типы предоставляют ограниченный интерфейс поверх базовых контейнеров + template + concept is_container_adapter = std::same_as> || + std::same_as> || + std::same_as>; + + // Ассоциативные контейнеры (универсальная проверка) + // @brief Проверяет, является ли тип упорядоченным ассоциативным контейнером + // @details Обнаруживает контейнеры, хранящие элементы в отсортированном порядке: + // - std::map - словарь с уникальными ключами + // - std::multimap - словарь с возможностью дубликатов ключей + // - std::set - множество уникальных элементов + // - std::multiset - множество с возможностью дубликатов + // Использует key_compare для упорядочивания (обычно std::less) + template + concept is_associative_container = has_iterator && + has_key_type && + (has_mapped_type || !has_mapped_type) && // Для map и set + (std::same_as> || + std::same_as> || + std::same_as> || + std::same_as>); + + // Неупорядоченные ассоциативные контейнеры + // @brief Проверяет, является ли тип неупорядоченным ассоциативным контейнером + // @details Обнаруживает контейнеры, использующие хеширование: + // - std::unordered_map - хеш-таблица с уникальными ключами + // - std::unordered_multimap - хеш-таблица с дубликатами ключей + // - std::unordered_set - хеш-множество уникальных элементов + // - std::unordered_multiset - хеш-множество с дубликатами + // Используют хеш-функции и сравнение на равенство вместо упорядочивания + template + concept is_unordered_associative_container = has_iterator && has_key_type && (has_mapped_type || !has_mapped_type) && + (std::same_as> || + std::same_as> || + std::same_as> || + std::same_as>); + + // Кортежи и пары + // @brief Проверяет, является ли тип кортежоподобным + // @details Обнаруживает типы, которые можно разбирать через tuple_size и tuple_element: + // - std::tuple - гетерогенный кортеж + // - std::pair - пара из двух элементов + // - std::array - также удовлетворяет этому концепту + // - Пользовательские кортежи с специализацией tuple_size + // Исключает строки и контейнеры для избежания ложных срабатываний + template + concept is_tuple_like = requires(T t) { + std::tuple_size::value; + requires std::tuple_size::value >= 0; + } && !is_string && !is_sequence_container; // Исключаем конфликты + + // Массивы + // @brief Проверяет, является ли тип встроенным массивом + // @details Обнаруживает C-style массивы: int[10], char[5], etc. + // Проверяет, что размер массива больше 0 (не incomplete array) + template + concept is_fixed_array = std::is_array_v && std::extent_v > 0; + + // @brief Проверяет, является ли тип std::array + // @details Обнаруживает std::array с конкретным размером + // Полезно для алгоритмов, которые требуют знания размера на этапе компиляции + template + concept is_std_array = std::same_as>; + + // Универсальные концепты для категоризации + // @brief Проверяет, является ли тип любым контейнером + // @details Всеобъемлющий концепт для обнаружения контейнеров любого типа: + // - Последовательные контейнеры + // - Ассоциативные контейнеры + // - Адаптеры контейнеров + // - Массивы (C-style и std::array) + // Основной концепт для обобщенных алгоритмов работы с контейнерами + template + concept is_any_container = is_sequence_container || + is_associative_container || + is_unordered_associative_container || + is_container_adapter || + is_fixed_array || + is_std_array>; + + // @brief Проверяет, является ли тип итерируемым + // @details Обнаруживает любые типы, по которым можно итерироваться: + // - Контейнеры STL с begin()/end() + // - Массивы (работают с std::begin/std::end) + // - Кортежи (хотя итерирование по ним особое) + // Самый общий концепт для range-based for и алгоритмов + template + concept is_iterable = has_iterator || is_fixed_array || is_tuple_like; + + // @brief Проверяет, имеет ли тип размер + // @details Обнаруживает типы, у которых можно узнать размер: + // - Контейнеры с методом size() + // - Массивы с известным размером + // - Кортежи с известным количеством элементов + // Важно для алгоритмов, требующих предварительного знания размера + template + concept is_sized = has_size || is_fixed_array || is_tuple_like; + + // Концепт для "неподдерживаемых" типов + // @brief Проверяет, является ли тип неподдерживаемым + // @details Обнаруживает типы, которые не входят в известные категории: + // - Пользовательские типы без ожидаемого интерфейса + // - Специфичные типы из сторонних библиотек + // - Типы, для которых нет специализированной обработки + // Используется для static_assert и генерации понятных ошибок + template + concept not_supported = !(std::integral || + std::floating_point || + is_string || + is_any_container || + is_tuple_like || + std::is_pointer_v); + + // Вспомогательные концепты для метапрограммирования + // @brief Проверяет, имеет ли тип семантику ключ-значение + // @details Обнаруживает контейнеры, которые поддерживают доступ по ключу: + // - map и unordered_map (operator[]) + // - set и unordered_set (хотя у set нет разделения на key/value) + // Полезно для алгоритмов работы со словарями и ассоциативными массивами + template + concept has_key_value_semantics = is_associative_container || + is_unordered_associative_container || + requires(T t, typename T::key_type key) { t[key]; }; + + // @brief Проверяет, является ли тип контейнером с непрерывной памятью + // @details Обнаруживает контейнеры, элементы которых хранятся в непрерывной памяти: + // - std::vector - динамический массив + // - C-style массивы + // - std::array - статический массив + // Критически важно для низкоуровневых операций и взаимодействия с C API + template + concept is_contiguous_container = std::same_as> || + is_fixed_array || + is_std_array>; + + // Концепты для алгоритмов + // @brief Проверяет, поддерживает ли контейнер emplace-операцию + // @details Обнаруживает контейнеры, которые могут создавать элементы на месте: + // - vector::emplace_back(), map::emplace(), etc. + // Позволяет избежать лишних копирований и перемещений + template + concept can_emplace = requires(Container c, Value&& v) { c.emplace(std::forward(v)); }; + + // @brief Проверяет, поддерживает ли контейнер добавление в начало + // @details Обнаруживает контейнеры с push_front(): + // - deque, list, forward_list + // Полезно для алгоритмов, работающих с очередями и стеками + template + concept can_push_front = requires(Container c, Value&& v) { c.push_front(std::forward(v)); }; + + // @brief Проверяет, поддерживает ли контейнер добавление в конец + // @details Обнаруживает контейнеры с push_back(): + // - vector, deque, list + // Важно для алгоритмов, которые строят контейнеры последовательно + template + concept can_push_back = requires(Container c, Value&& v) { c.push_back(std::forward(v)); }; + + // @brief Проверяет, поддерживает ли контейнер поиск по ключу + // @details Обнаруживает контейнеры с методом find(): + // - map, set, unordered_map, unordered_set + // Ключевой концепт для алгоритмов поиска и проверки существования элементов + template + concept can_find = requires(Container c, Key&& key) { c.find(std::forward(key)); }; }