Modern, type-safe, header-only C++23 library that provides seamless random value generation across any container with full compile-time type checking and optimization.
Supports all numeric types including char and bool, works with all STL containers and user-defined objects, allows to generate unique numbers. The library is lightweight. It does not imports any STL container.
If you want to know why it's better than rand(), please read this article on learncpp.
Library tested on Windows 10 22h2 with CLang 22.1.4 (Msys64, x86_64-w64-windows-gnu).
Copy generate.hpp to your include directory:
cp generate.hpp /path/to/your/include/And #include it in your project:
#include "generate.hpp"Use CMake 3.21 and higher to integrate as a package:
find_package(generate-random REQUIRED)
target_link_libraries(your_target generate-random::generate-random)If you're using CLion 2024.1 and higher, you need to add path to generate.hpp in your CMakeLists.txt and create new build target using add_executable. Let's assume that you've downloaded generate.hpp to the Lib directory:
include_directories("Lib")
add_executable(main main.cpp)Now reload CMake project and #include library in main.cpp:
#include "generate.hpp"Select main target at the top and try to build it. Now you should be able to use anything from rnd namespace. If you want to run single files, create multiple targets and include generate.hpp to each source file.
If your toolchain (MinGW on Windows) refuses to compile, try to install CLang toolchain (MSYS2 on Windows). CLang is awesome compiler for beginners because it displays readable compilation errors!
Full CMakeLists
This is my CMakeLists for one of my C++ projects:
cmake_minimum_required(VERSION 3.28)
project(CPP)
set(CMAKE_CXX_STANDARD 23)
# My libraries
include_directories(
"C:/Cfg/CPP/icecream-cpp"
"C:/Cfg/CPP/generate-random"
"Lib"
)
# Auto-generate CMake targets from .cpp files
file(GLOB SOURCES
"src/*.cpp"
"main.cpp")
set(REL_SOURCES)
foreach(source IN LISTS SOURCES)
file(RELATIVE_PATH rel_source "${CMAKE_CURRENT_SOURCE_DIR}" "${source}")
if(rel_source MATCHES "^\\.")
continue()
endif()
list(APPEND REL_SOURCES ${rel_source})
endforeach()
set(SOURCES ${REL_SOURCES})
#message("SOURCES after filter: ${SOURCES}")
foreach(source_file IN LISTS SOURCES)
get_filename_component(file_name ${source_file} NAME_WE)
get_filename_component(dir ${source_file} DIRECTORY)
if(NOT dir STREQUAL "")
string(REPLACE " " "_" dir ${dir})
string(REPLACE "/" "_" dir ${dir})
set(target_name ${dir}_${file_name})
else()
set(target_name ${file_name})
endif()
add_executable(${target_name} ${source_file})
target_compile_options(${target_name} PRIVATE -Wno-system-headers)
endforeach()#include "generate.hpp"
#include <vector>
using rnd::generate, rnd::generate_uniq, std::vector;
// 10 random integers between 1 and 100
auto numbers = generate<vector<int>>(10, 1, 100);
// 5 random characters between 'a' and 'z'
auto letters = generate<vector<char>>(5, 'a', 'z');
// 8 random floating point numbers between 0.0 and 1.0
auto doubles = generate<vector<double>>(8, 0.0, 1.0);
// 10 unique integers (guaranteed no duplicates)
auto unique_ints = generate_uniq<vector<int>>(10, 1, 50);If you want to see the contents of a container, use IceCream library:
using rnd::generate, std::vector;
auto ivec = generate<vector<int>>(5, 1, 10, 12345);
IC(ivec); // [3, 6, 2, 5, 10]
auto cvec = generate<vector<char>>(4, 'a', 'e', 12345);
IC(cvec); // ['c', 'b', 'e', 'b']More examples can be found in the old test.
Generates a container filled with random values.
template<
typename Container,
arithmetic Item = typename Container::value_type,
uniform_generator Generator = std::mt19937_64,
typename Seed = unsigned int
>
Container generate(
size_t count = 10,
Item min = 1, Item max = 26,
Seed seed = std::random_device{}()
);Parameters:
count– Number of elements to generatemin– Minimum value (inclusive)max– Maximum value (inclusive)seed– Initial seed for reproducibility (default: random)
Returns: A filled container of specified type
Examples:
// Vector of 5 random integers [1, 20]
auto vec = rnd::generate<std::vector<int>>(5, 1, 20, 42);
// Set of 10 unique integers [1, 100]
auto set = rnd::generate<std::set<int>>(10, 1, 100);
// Deque of 3 random characters ['A', 'Z']
auto deq = rnd::generate<std::deque<char>>(3, 'A', 'Z');Note
set, unordered_set, map, unordered_map and other similar associative containers excludes duplicates: if the generated value already exists, it will not be added! If count > max, the final container size will be max - min + 1. Otherwise it will be equal to count.
Generates a container with unique random values. For ranges smaller than the requested count, the result will contain all possible values in the [min, max] range.
template<
typename Container,
arithmetic Item = typename Container::value_type,
uniform_generator Generator = std::mt19937_64,
typename Seed = unsigned int
>
Container generate_uniq(
size_t count = 10,
Item min = 1, Item max = 26,
Seed seed = std::random_device{}()
);Examples:
// Unique integers [1, 10] – if count > 10, result size = 10
auto vec = rnd::generate_uniq<std::vector<int>>(20, 1, 10);
// 10 unique floats [10.11, 50.45]
auto deq = generate_uniq<deque<float>>(10, 10.11f, 50.45f);
// Unique characters in set
auto charset = rnd::generate_uniq<std::set<char>>(5, 'a', 'z');generate() always calls generate_uniq() for associative containers like set, unordered_set, etc.
generate<set<int>>(5);
// same as...
generate_uniq<set<int>>(5);Generates containers of boolean values. For associative containers, automatically clamps to 0-2 unique values.
template<
typename Container,
uniform_generator Generator = std::mt19937_64,
typename Seed = unsigned int
>
Container generate_bool(
size_t count = 10,
Seed seed = std::random_device{}()
);Examples:
// Vector of 10 random booleans
auto flags = rnd::generate_bool<std::vector<bool>>(10);
// Vector of 10 random integers 0 or 1
auto vec = rnd::generate_bool<std::vector<int>>(10);
// Set of booleans (automatically clamped to size = 2)
auto boolset = rnd::generate_bool<std::set<int>>(10);generate_bool() accepts any integral type that can be cast to 0 or 1: bool, short, int, size_t, ...:
using rnd::generate_bool, std::vector;
auto v1 = generate_bool<vector<int>>(10);
auto v2 = generate_bool<vector<long>>(10);
auto v3 = generate_bool<vector<size_t>>(10);The function will successfully fill the containers with zeros and ones.
generate() always calls generate_bool() for containers with bool items like vector<bool>, deque<bool>, etc.:
generate<vector<bool>>(5);
// same as...
generate_bool<vector<bool>>(5);generate() and generate_uniq() always calls generate_bool() if min = 0 and max <= 1 because it's more efficient for [0, 1] range:
generate<vector<int>>(5, 0, 1);
// same as...
generate_uniq<vector<int>>(5, 0, 1);
// same as...
generate_bool<vector<bool>>(5);You can call the generate() if you need any random numbers, it will take care of the rest. And it's also shorter.
Caution
Don't use using namespace rnd and using namespace std! The library defines various concepts and functions. Their names may conflict with your project and the std namespace (e.g., rnd::array and std::array). It's good practice to import only what you'll actually use:
using std::array,
rnd::generate_bool,
rnd::generate_uniq,
rnd::generate;The library does not imports any STL container and it works with any container that supports methods from the table below:
| Container | Insertion Method | Unique Numbers |
|---|---|---|
vector |
push_back() |
✓ |
deque |
push_back() |
✓ |
list |
push_back()/push_front()/insert() |
✓ |
forward_list |
push_front() |
✓ |
array |
fill() |
✓ |
set |
insert() |
✓ (enforced) |
unordered_set |
insert() |
✓ (enforced) |
map |
insert() |
✓ (enforced) |
unordered_map |
insert() |
✓ (enforced) |
queue |
push() |
✓ |
stack |
push() |
✓ |
So if you have an object that has one of these methods (e.g insert()), you can pass it to the generate(). If you want to add new methods support, see "custom containers" below
| Category | Types |
|---|---|
| Integers | int8_t, uint8_t, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long, __int128_t, __uint128_t |
| Floating-Point | float, double, long double |
| Characters | char, signed char, unsigned char, wchar_t, char8_t, char16_t, char32_t |
| Boolean | bool |
Extensions available when _LIBCPP_HAS_INT128 is defined.
#include "generate.hpp"
#include <vector>
#include <iostream>
int main() {
// Generate unique integers 1-52 for a deck
auto deck = rnd::generate<std::vector<int>>(52, 1, 52);
std::cout << "Shuffled deck: ";
for (int card : deck) {
std::cout << card << " ";
}
std::cout << "\n";
return 0;
}#include "generate.hpp"
#include <set>
int main() {
// Generate test scores (unique values)
auto scores = rnd::generate<std::set<int>>(20, 0, 100, 42);
// Generate random timeout values
auto timeouts = rnd::generate<std::deque<double>>(10, 0.1, 5.0, 42);
return 0;
}#include "generate.hpp"
#include <vector>
#include <cmath>
int main() {
// Generate random points for Monte Carlo π estimation
auto x_coords = rnd::generate<std::vector<double>>(1000, 0.0, 1.0, 999);
auto y_coords = rnd::generate<std::vector<double>>(1000, 0.0, 1.0, 1000);
int inside = 0;
for (size_t i = 0; i < x_coords.size(); ++i) {
double dist = std::sqrt(x_coords[i] * x_coords[i] + y_coords[i] * y_coords[i]);
if (dist <= 1.0) inside++;
}
double pi_estimate = 4.0 * inside / 1000.0;
std::cout << "π ≈ " << pi_estimate << "\n";
return 0;
}You can find more examples in tests firectory.
Use the same seed to get identical random sequences:
// First run
auto v1 = rnd::generate<std::vector<int>>(5, 1, 10, 12345);
// Later – same sequence as v1
auto v2 = rnd::generate<std::vector<int>>(5, 1, 10, 12345);
// v1 == v2 ✓The library defines comprehensive C++23 concepts to ensure compile-time type safety:
// These concepts are available for custom extensions:
namespace rnd {
template<typename T> concept rand_integer; // Valid integer types
template<typename T> concept rand_floating; // Valid floating types
template<typename T> concept rand_arithmetic; // Any numeric type
template<typename T> concept boolean; // Boolean type
template<typename T> concept character; // All character types
template<typename C> concept container_push_back;
template<typename C> concept container_push;
template<typename C> concept associative_container;
template<typename C> concept unordered_set;
template<typename C> concept ordered_set;
// ... and more
}Extend the library by creating custom distributions for specific types:
namespace rnd::detail {
// Define your custom distribution
template<>
inline auto make_distribution<MyCustomType>(MyCustomType min, MyCustomType max) {
return my_custom_distribution(min, max);
}
}
// Now use generate() with your custom type
auto container = rnd::generate<std::vector<MyCustomType>>(10, 0, 100);The library supports any container with this methods. You can extend containers support by adding a new fill_container() template with concepts constraints:
namespace rnd {
template<typename Cont>
concept container_add = requires(Cont c, typename Cont::value_type v) {
c.add(v);
};
namespace detail {
// Define your custom filler
template<container_add Container, typename Distribution, uniform_generator Generator>
inline void fill_container(Container& container, size_t count, Distribution& dist, Generator& gen) {
for (size_t i = 0; i < count; ++i) {
container.add(dist(gen));
}
}
}
}- C++23 capable compiler (GCC 13+, Clang 17+, MSVC 2022+)
- CMake 3.21+
- Catch2 (for testing)
mkdir build
cd build
cmake ..
cmake --build .ctestOr run individual test suites:
./test_vector
./test_set
./test_deque
./test_uniqueIf you're working in CLion, create a new project from this repository sources. Make sure that CMakeLists.txt file is matches the CMakeLists in the repository. Wait for cmake project loading and select "All CTest" target at the top. CLion should run all tests automatically after pressing "Build" button.
This library is released under the MIT License. You can use it as you want. See LICENSE.txt for details.
Contributions are welcome! Please feel free to submit issues or pull requests to improve the library.