Используйте библиотеку <random>
вместо устаревшей и подверженной ошибкам std::rand
(использование оператора по модулю для получения случайного целого числа в диапазоне - распространенная ошибка ) , См. Почему новая случайная библиотека лучше, чем std :: rand ()? для получения дополнительной информации.
#include <iostream>
#include <random>
int main()
{
std::mt19937 engine{std::random_device{}()};
std::uniform_int_distribution<int> dist{0, 3};
switch (dist(eng)) {
case 0:
std::cout << "...\n";
break;
case 1:
std::cout << "...\n";
break;
case 2:
std::cout << "...\n";
break;
case 3:
std::cout << "...\n";
break;
}
}
Здесь мы сначала создаем std::mt19937
двигатель, который производит равномерно распределенные целые числа в полуоткрытом диапазоне [0, 2 32 ), и заполняет его, используя std::random_device
, который должен генерировать недетерминированный c номер (может быть реализован, например, с использованием системного времени). Затем мы создаем std::uniform_int_distribution
для сопоставления случайных чисел, сгенерированных механизмом, с целыми числами в включающем интервале [0, 3], вызывая его с механизмом в качестве аргумента.
Это можно обобщить, взяв для печати диапазон строк:
template <typename RanIt, typename F>
decltype(auto) select(RanIt begin, RanIt end, F&& f)
{
if (begin == end) {
throw std::invalid_argument{"..."};
}
thread_local std::mt19937 engine{std::random_device{}()};
using index_t = long long; // for portability
std::uniforn_int_distribution<index_t> dist{0, index_t{end - begin - 1}};
return std::invoke(std::forward<F>(f), begin[dist(engine)]);
}
int main()
{
const std::array<std::string, 4> messages {
// ...
};
select(messages.begin(), messages.end(),
[](const auto& string) {
std::cout << string << '\n';
});
}
Здесь мы берем пару итераторов произвольного доступа и вызываемый объект для поддержки выбора элемента из произвольного произвольно доступного диапазона и выполнения произвольной операции с ним.
Сначала мы проверяем, является ли диапазон пустым, в этом случае выбор невозможен, и throw
сообщает об ошибке исключение .
Затем мы создаем std::mt19937
движок thread_local
(то есть каждый поток имеет свой собственный движок), чтобы предотвратить скачки данных . Состояние механизма поддерживается между вызовами, поэтому мы запускаем его только один раз для каждого потока.
После этого мы создаем std::uniform_int_distribution
для генерации случайного индекса. Обратите внимание, что мы использовали long long
вместо typename std::iterator_traits<RanIt>::difference_type
: std::uniform_int_distribution
гарантированно работает только с short
, int
, long
, long long
, unsigned short
, unsigned int
, unsigned long
и unsigned long long
, поэтому, если difference_type
равно signed char
или расширенный целочисленный тип со знаком, это приводит к неопределенному поведению. long long
является самым большим поддерживаемым целочисленным типом со знаком, и мы используем фигурную инициализацию , чтобы предотвратить сужающие преобразования .
Наконец, мы std::forward
вызываемый объект и std::invoke
его с выбранным элементом. Спецификатор decltype(auto)
обеспечивает сохранение типа и категории значения вызова.
Мы вызываем функцию с помощью std::array
и лямбда-выражение , которое печатает выбранный элемент.
Начиная с C ++ 20, мы можем ограничить шаблон функции, используя понятия:
template <std::random_access_iterator RanIt,
std::indirectly_unary_invocable<RanIt> F>
decltype(auto) select(RanIt begin, RanIt end, F&& f)
{
// ...
}
До C ++ 20 мы также можем использовать SFINAE:
template <typename RanIt, typename F>
std::enable_if_t<
std::is_base_of_v<
std::random_access_iterator_tag,
typename std::iterator_traits<RanIt>::iterator_category
>,
std::invoke_result_t<F, typename std::iterator_traits<RanIt>::value_type>
> select(RanIt begin, RanIt end, F&& f)
{
// ...
}