add rec to audio and base modern concepts
This commit is contained in:
@@ -2,12 +2,15 @@
|
||||
|
||||
* Это очень нужная коллекция фрагментов рабочего кода для возможного упрощения жизни.*
|
||||
|
||||
Автор не претендует на чистоту, реализации и верность исполнения, поэтому, если вы думаете, что можете сделать это по-другому, то пожалуйста, сделайте это.
|
||||
Автор не претендует на чистоту, реализации и верность исполнения, поэтому если вы думаете, что можете сделать это по-другому, то пожалуйста, сделайте это.
|
||||
|
||||
Пожалуйста, смотрите пример реализации в /bin/main.cpp и tests/...
|
||||
Пожалуйста, смотрите пример реализации в /bin/имя_желаемого.cpp и tests/...
|
||||
|
||||
Что тут:
|
||||
- audio - набор методов по работе со звуком: воспроизведение, запись, работа с массивом звуков и т.п.
|
||||
- concepts - набор разнообразных реализаций концептов
|
||||
|
||||
|
||||
- iterators - набор разнообразных реализаций итераторов
|
||||
- log - реализация лдогирования
|
||||
- patterns - набор различных паттернов проектирования
|
||||
|
||||
41
bin/audio.cpp
Normal file
41
bin/audio.cpp
Normal 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);
|
||||
}
|
||||
24
bin/main.cpp
24
bin/main.cpp
@@ -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,27 +14,15 @@
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
executable(
|
||||
meson.project_name(),
|
||||
'main.cpp',
|
||||
'audio.cpp',
|
||||
dependencies : deps,
|
||||
cpp_args: args,
|
||||
include_directories : inc
|
||||
|
||||
@@ -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
3
src/hack/audio/info.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Вся работа с аудио на данный момент крутиться во круг wav.
|
||||
Т.е. что-то типа - воспроизвести/записать в mp3 или что-то подобное требует своей реализации, которой
|
||||
буду заниматься по мерее её необходимости !!!
|
||||
55
src/hack/audio/manager.hpp
Normal file
55
src/hack/audio/manager.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -1,53 +1,86 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
// пока только wav
|
||||
inline void play(std::vector<double>& samples, int sample_rate)
|
||||
// Колбэк для воспроизведения из файла.
|
||||
static int file_callback(const void*, void* output_buffer, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* data)
|
||||
{
|
||||
PaError err = Pa_Initialize();
|
||||
if (err != paNoError)
|
||||
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)
|
||||
{
|
||||
hack::error()("PortAudio error: ", Pa_GetErrorText(err));
|
||||
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;
|
||||
err = Pa_OpenDefaultStream(&stream,
|
||||
0, // Нет входных каналов
|
||||
1, // 1 выходной канал (моно)
|
||||
paFloat32, // 32-bit floating-point output
|
||||
PaError err = Pa_OpenDefaultStream(&stream,
|
||||
0, // нет входных каналов
|
||||
channels, // количество каналов
|
||||
paFloat32, // 32-bit floating-point
|
||||
sample_rate,
|
||||
FRAMES_PER_BUFFER,
|
||||
callback,
|
||||
&samples);
|
||||
&data);
|
||||
|
||||
if (err != paNoError)
|
||||
{
|
||||
@@ -56,21 +89,42 @@ namespace hack::audio
|
||||
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 мс между проверками
|
||||
while (Pa_IsStreamActive(stream)) Pa_Sleep(100);
|
||||
|
||||
// очистка
|
||||
Pa_CloseStream(stream);
|
||||
Pa_Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
inline void play(const std::vector<double>& samples, int sample_rate, int channels = 1)
|
||||
{
|
||||
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()("Cannot open audio file: ", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
145
src/hack/audio/record.hpp
Normal file
145
src/hack/audio/record.hpp
Normal 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)");
|
||||
}
|
||||
}
|
||||
@@ -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)); };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user