Skip to content

JoyHak/generate-random

Repository files navigation

Language: C++ License: MIT Header-only

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).

Quick Start

Installation

Copy generate.hpp to your include directory:

cp generate.hpp /path/to/your/include/

And #include it in your project:

#include "generate.hpp"

CMake

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)

JetBrains

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()

Basic Usage

#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.

API Reference

Main Functions

generate()

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 generate
  • min – 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.

generate_uniq()

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);

generate_bool()

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;

Supported Containers

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

Supported Types

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.

Examples

Shuffle a Deck

#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;
}

Test Data Generation

#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;
}

Monte Carlo Simulation

#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.

Advanced Usage

Reproducible Generation

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 ✓

Concepts

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
}

Custom Distributions

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);

Custom Containers

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));
            }
        }
    }
}

Build & Test

Prerequisites

  • C++23 capable compiler (GCC 13+, Clang 17+, MSVC 2022+)
  • CMake 3.21+
  • Catch2 (for testing)

Build

mkdir build
cd build
cmake ..
cmake --build .

Run Tests

ctest

Or run individual test suites:

./test_vector
./test_set
./test_deque
./test_unique

If 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.

License

This library is released under the MIT License. You can use it as you want. See LICENSE.txt for details.

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests to improve the library.