Skip to content

BS::thread_pool v5.0.0

Latest
Compare
Choose a tag to compare
@bshoshany bshoshany released this 20 Dec 02:08

v5.0.0 (2024-12-19)

  • A major new release with many new features, improvements, bug fixes, and performance optimizations! Please note that code written using previous releases may need to be modified to work with the new release. The changes needed to migrate to the new API are explicitly indicated below for your convenience.
  • Highlights:
    • Added support for C++20 and C++23, while maintaining full C++17 compatibility. In C++20, the library can now optionally be imported as a module using import BS.thread_pool on Clang, GCC, and MSVC. In C++23, both the library itself and the test program can now optionally import the C++ Standard Library as a module using import std on supported compilers and platforms. Extensive documentation has been added to README.md on how to use these features, to ease the transition.
    • Optional features are now enabled via a bitmask template parameter instead of macros, using the flags BS::tp::priority, BS::tp::pause, and BS::tp::wait_deadlock_checks. This makes the optional features easier to use, allows multiple thread pools with different features to coexist, and makes the library compatible with C++20 modules. Exception handling is now disabled automatically if exceptions are disabled, instead of using a macro.
    • Added optional native extensions for non-portable features using the operating system's native API: setting the priority and affinity for processes and threads, and setting thread names. These have been tested on the latest versions of Windows, Ubuntu, and macOS.
    • This library is now back to being a true single-header library, with a single header file BS_thread_pool.hpp. The utility classes have been combined into the main header file. BS::timer has been removed, BS::signaller has been replaced with BS::binary_semaphore and BS::counting_semaphore (in C++17 mode only), and BS::synced_stream now supports multiple output streams.
    • Cleanup functions can now be defined to complement the initialization functions. Both initialization and cleanup functions can now optionally take the index of the thread as an argument.
    • Parallelization member functions no longer need type casting or template parameters if the start and end indices are of different types.
    • The worker function no longer incorrectly reads shared variables while the mutex is unlocked.
    • The type aliases BS::this_thread::optional_index and BS::this_thread::optional_pool have been removed. Instead, BS::this_thread::get_index() returns std::optional<std::size_t>, and BS::this_thread::get_pool() returns std::optional<void*>. The latter must be cast to the correct instantiation of the BS::thread_pool class template before using any member functions.
    • The thread pool version is now accessible using the object BS::thread_pool_version, a constexpr struct of type BS::version with the members major, minor, and patch. This works even if importing the library as a C++20 module, unlike the version macros.
    • The type priority_t, used to set priorities, is now defined as std::int8_t, which means it takes values from -128 to +127. The pre-defined priorities in BS::pr, such as BS::pr::highest or BS::pr::lowest, have been updated accordingly.
    • Exceptions thrown by detached tasks are now caught and prevented from propagating, so that they do not terminate the program. Exceptions thrown by submitted tasks are still rethrown when calling get() on the future, as before.
    • Parallelization member functions no longer destruct objects prematurely under certain circumstances.
    • The test program has been expanded with many new tests for both old and new features. It can also import both the thread pool module using import BS.thread_pool (in C++20 and later) and the C++ Standard Library module using import std (in C++23) if the appropriate macros are defined, and read default command line arguments from a default_args.txt file for debugging purposes.
    • Added new and improved benchmarks using a highly-optimized multithreaded algorithm which generates a plot of the Mandelbrot set, utilizing a normalized iteration count algorithm and linear interpolation to create smooth coloring.
    • The type BS::concurrency_t has been removed; use std::size_t instead.
  • C++20 and C++23 support:
    • This library now officially supports C++20 and C++23 in addition to C++17. If compiled with C++20 and/or C++23 support (e.g. using the compiler flag -std=c++23 in Clang/GCC or /std:c++latest on MSVC), the library will make use of newly available features for maximum performance, reliability, and usability.
      • To be clear, the library is still fully compatible with any C++17 standard-compliant compiler. I have no plans to remove C++17 support at the moment, as it is still the most widely used C++ standard among developers, but that might change in the future.
    • If C++20 features are available, the library can be imported as a module using import BS.thread_pool. This is now the officially recommended way to use the library, as it has many benefits, such as faster compilation times, better encapsulation, no namespace pollution, no include order issues, easier maintainability, simpler dependency management, and more.
      • The module file itself is BS.thread_pool.cppm, located in the modules folder, and it is just a thin wrapper around the header file BS_thread_pool.hpp.
      • The constexpr flag BS::thread_pool_module indicates whether the thread pool library was compiled as a module.
      • To my knowledge, BS::thread_pool is one of the only popular C++ libraries that are currently available as a C++20 module (and certainly the only thread pool library). This feature has been tested with the latest versions of Clang, GCC, and MSVC. Unfortunately, C++20 modules are still (4 years later!) not fully implemented in all compilers, and each compiler implements them differently; for instructions on how to compile and import the BS.thread_pool module in each compiler, please see README.md.
      • Known issues:
        • GCC v14.2.0 (latest version at the time of writing) appears to have an internal compiler error when compiling programs containing modules (or at least, this particular module) with any optimization flags other than -Og enabled. Until this is fixed, if you wish to use compiler optimizations, please either include the library as a header file or use a different compiler.
        • On macOS, Apple Clang v16.0.0 (latest version at the time of writing) does not support C++20 modules. Please either install the latest version of LLVM Clang using Homebrew, or include the library as a header file.
        • Visual Studio Code's C/C++ extension v1.23.2 (latest version at the time of writing) does not yet support modules. My temporary solution for that, as demonstrated in the test program, is to define the macro BS_THREAD_POOL_TEST_IMPORT_MODULE (see below) when compiling the test program, but not when editing in the IDE. If the macro is enabled, the module is imported via import BS.thread_pool, otherwise the header file is included using #include "BS_thread_pool.hpp" as usual.
    • If C++23 features are available, both the library and the test program can now import the C++ Standard Library as a module using import std. To enable this, define the macro BS_THREAD_POOL_IMPORT_STD at compilation time. This is currently only officially supported by recent versions of MSVC with Microsoft STL or LLVM Clang (not Apple Clang) with LLVM libc++. It is not supported by GCC with any standard library, Clang with any standard library other than libc++, any compiler with GNU libstdc++, or any other compiler.
      • If BS_THREAD_POOL_IMPORT_STD is defined, then you must also import the library itself as a module. If the library is included as a header file, this will force the program that included the header file to also import std, which is not desirable and can lead to compilation errors if the program #includes any Standard Library header files.
      • Defining the macro before importing the module will not work, as modules cannot access macros defined in the program that imported them. Instead, define the macro as a compiler flag, e.g. -D BS_THREAD_POOL_IMPORT_STD (or /D for MSVC).
      • The constexpr flag BS::thread_pool_import_std indicates whether the thread pool library was compiled with import std. Note that the flag will be false if BS_THREAD_POOL_IMPORT_STD is defined but the compiler or standard library does not support importing the C++ Standard Library as a module.
    • If C++20 features are available, the pool will use std::jthread instead of std::thread. This allows considerable simplification and added safety, since the threads no longer need to be manually joined, and std::stop_token is used to stop the workers automatically when destructing the threads. This eliminates the need for the destroy_threads() member function, as well as the workers_running flag, which are now only used in C++17 mode.
    • If C++20 features are available, the library will use concepts to enforce the signature of the initialization function and to selectively enable member functions related to pausing only if pausing is enabled. In C++17 mode, the library will use SFINAE to achieve essentially the same effect.
    • If C++23 features are available, the task queue will use std::move_only_function<void()> instead of std::function<void()>. This allows submit_task() to work without using a shared pointer, which should increase performance.
    • API migration: All of the C++20/C++23 features listed above are either automatically applied based on compiler settings or optional. If you are still using C++17, or if you are using C++20 or C++23 but do not wish to import the thread pool library and/or the C++ Standard Library as a module, no changes are needed.
  • Optional features overhaul:
    • All optional features are now enabled via a bitmask template parameter instead of macros. This works using if constexpr, std::conditional_t, and concepts (in C++20 and later) or SFINAE (in C++17).
      • This change makes the optional features much easier and more intuitive to use, as you no longer need to define any macros before including the header file.
      • Additionally, it allows you to have multiple thread pools in the same program with different optional features enabled or disabled. For example, you can have one pool with task priority enabled and another without.
      • Most importantly, this makes it possible to import the library as a C++20 module, as macros cannot be read by imported modules.
    • The bitmask flags are members of the BS::tp enumeration:
      • BS::tp::priority enables task priority (previously enabled via the macro BS_THREAD_POOL_ENABLE_PRIORITY, which has been removed).
      • BS::tp::pause enables pausing the pool (previously enabled via the macro BS_THREAD_POOL_ENABLE_PAUSE, which has been removed).
      • BS::tp::wait_deadlock_checks enables deadlock checks in wait()/wait_for()/wait_until() (previously enabled via the macro BS_THREAD_POOL_ENABLE_WAIT_DEADLOCK_CHECK, which has been removed).
      • The default is BS::tp::none, which disables all optional features.
    • Convenience aliases are defined as follows:
      • BS::light_thread_pool disables all optional features (equivalent to BS::thread_pool with the default template parameter, that is, BS::thread_pool<BS::tp::none>).
      • BS::priority_thread_pool enables task priority (equivalent to BS::thread_pool<BS::tp::priority>).
      • BS::pause_thread_pool enables pausing the pool (equivalent to BS::thread_pool<BS::tp::pause>).
      • BS::wdc_thread_pool enables wait deadlock checks (equivalent to BS::thread_pool<BS::tp::wait_deadlock_checks>).
      • There are no aliases with multiple features enabled; if this is desired, you must either pass the template parameter explicitly or define your own alias. Note that the parameter is a bitmask, so to enable multiple features, you need to use the bitwise OR operator |, e.g. BS::thread_pool<BS::tp::priority | BS::tp::pause> to enable both task priority and pausing.
    • The macro BS_THREAD_POOL_DISABLE_EXCEPTION_HANDLING has been removed. Exception handling is disabled automatically if exceptions are disabled, based on whether the feature-test macro __cpp_exceptions is defined.
    • The exception thrown by wait deadlock checks is now BS::wait_deadlock instead of BS::thread_pool::wait_deadlock, to avoid having to deal with different template parameters.
    • The macro BS_THREAD_POOL_LIGHT_TEST has been removed from the test program, as all optional features are now tested by enabling them selectively via the template parameter, so there is no need to compile with different macros.
    • If for some reason you forgot which options you enabled when creating the pool, the static constexpr members priority_enabled, pause_enabled, and wait_deadlock_checks_enabled can be used to check if the corresponding features are enabled.
    • API migration:
      • BS::thread_pool can still be used without the template parameter, for backwards compatibility; this will create a thread pool with all optional features disabled. Therefore, if you did not use any of the optional features in existing code, no changes are needed.
      • If your code uses any of the optional features by defining macros before including the header file, please remove these macros, and instead either use one of the convenience aliases above or define the template parameter explicitly using the BS::tp enumeration when creating the pool.
      • If you use wait deadlock checks, you must now catch the exception BS::wait_deadlock instead of BS::thread_pool::wait_deadlock.
  • Native extensions:
    • While portability is one of my guiding principle when developing this library, non-portable features such as setting the thread priority using the operating system's native API are frequently requested by users. Starting with this release, the library includes native extensions, which are disabled by default.
    • Currently, the extensions provide the following functions (please see README.md for details on how to use them):
      • BS::get_os_process_affinity() and BS::set_os_process_affinity() to get and set the CPU affinity of the current process in a portable way. Should work on Windows and Linux, but not on macOS, as the native API does not allow it.
      • BS::get_os_process_priority() and BS::set_os_process_priority() to get and set the priority of the current process in a portable way. Should work on Windows, Linux, and macOS.
      • BS::this_thread::get_os_thread_affinity() and BS::this_thread::set_os_thread_affinity() to get and set the CPU affinity of the current thread in a portable way. Should work on Windows and Linux, but not on macOS, as the native API does not allow it.
      • BS::this_thread::get_os_thread_priority() and BS::this_thread::set_os_thread_priority() to get and set the priority of the current thread in a portable way. Should work on Windows, Linux, and macOS.
      • BS::this_thread::get_os_thread_name() and BS::this_thread::set_os_thread_name() to get and set the name of the current thread in a portable way, for debugging purposes. Should work on Windows, Linux, and macOS.
    • The native extensions may be enabled by defining the macro BS_THREAD_POOL_NATIVE_EXTENSIONS at compilation time.
      • Even if the macro is defined, the extensions are disabled automatically if a supported operating system (Windows, Linux, or macOS) is not detected.
      • Note that if you are using the library as a C++20 module, defining the macro before importing the module will not work, as modules cannot access macros defined in the program that imported them. Instead, define the macro as a compiler flag, e.g. -D BS_THREAD_POOL_NATIVE_EXTENSIONS (or /D for MSVC).
    • The macro BS_THREAD_POOL_ENABLE_NATIVE_HANDLES has been removed. The thread pool member function get_native_handles() is now part of the native extensions, so it is enabled using the macro BS_THREAD_POOL_NATIVE_EXTENSIONS.
    • Please note that the native extensions have only been tested on Windows 11 23H2, Ubuntu 24.10, and macOS 15.1. They have not been tested on older versions of these operating systems, other Linux distributions, or any other operating systems, and are therefore not guaranteed to work on every system. If you encounter any issues, please report them on the GitHub repository.
    • The test program only tests the native extensions if the macro BS_THREAD_POOL_NATIVE_EXTENSIONS is defined at compilation time. If importing the library as a module, please ensure that the macro is also enabled when compiling the module.
    • The constexpr flag BS::thread_pool_native_extensions indicates whether the thread pool library was compiled with native extensions enabled. Note that the flag will be false if BS_THREAD_POOL_NATIVE_EXTENSIONS is defined but the operating system is unsupported.
    • API migration: The native extensions are a brand new optional feature and do not require any changes to existing code.
  • Utility classes:
    • This library is now back to being a true single-header library, with a single header file BS_thread_pool.hpp. The utility classes (previously in a separate header BS_thread_pool_utils.hpp, which has been removed) have been combined into the main header file.
    • The BS::timer class has been removed from the library, since it doesn't really have anything to do with multithreading directly. However, it is still available in the test program if you want to use it.
    • The BS::signaller class has been removed from the library, and replaced with BS::binary_semaphore and BS::counting_semaphore, which are C++17 polyfills for the C++20 classes std::binary_semaphore and std::counting_semaphore. If C++20 features are available, the polyfills are not used, and instead are just aliases for the standard library classes. The reason is that semaphores can do the same thing that the signaller class was previously used for, but are much more versatile.
    • The BS::synced_stream class now supports printing to more than one output stream.
    • API migration:
      • If you previously included the BS_thread_pool_utils.hpp header file, this is no longer needed. Only include the header BS_thread_pool.hpp, or better yet, in C++20 or later, import the library as a module using import BS.thread_pool.
      • If you previously used the BS::timer class, it is no longer available in the header file, but if you still need it you can copy it into your program directly from the test program BS_thread_pool_test.cpp.
      • If you previously used the BS::signaller class, you can replace it with BS::binary_semaphore or BS::counting_semaphore. Previously, you defined an object BS::signaller signal, and then used signal.wait() to wait for the signal, and signal.ready() to unblock all waiting threads. Now, you can define an object BS::counting_semaphore signal(0), and use signal.acquire() to wait for the signal, and signal.release(num_threads) to unblock waiting threads; note that the number of threads to release must be passed explicitly, as the semaphore also allows you to unblock only some of them. Use BS::binary_semaphore if only one thread will be waiting at any given time.
      • If you previously used the BS::synced_stream class, no changes are needed.
  • Cleanup and initialization functions:
    • Using the new set_cleanup_func() member function, it is now possible to provide the pool with a cleanup function to run in each thread right before it is destroyed, which will happen when the pool is destructed or reset. See #152.
    • Both initialization and cleanup functions can now optionally take the index of the thread as an argument.
    • Added a warning in the documentation that both initialization and cleanup functions must not throw any exceptions, as that will result in program termination. Any exceptions must be handled explicitly within the function.
    • API migration: No changes to existing code are needed.
  • Parallelization index types:
    • All member functions which parallelize collections of tasks, namely detach_blocks(), detach_loop(), detach_sequence(), submit_blocks(), submit_loop(), and submit_sequence(), can now be called with start and end indices of different types.
    • Previously, the indices had to be of the same type, or the template parameter had to be explicitly specified; this is no longer needed, as the library will automatically cast the indices to a suitable common type.
    • This was already possible in v2.X.X and v3.X.X, where it was done using std::common_type, but I removed it in v4.X.X because std::common_type sometimes completely messed up the range of the loop. For example, the std::common_type of int and unsigned int is unsigned int, which means the loop will only use non-negative indices even if the int start index was negative, resulting in an integer overflow.
    • Starting with v5.0.0, the library uses a custom type trait BS::common_index_type to determine the common type of the indices. The common type of two signed integers or two unsigned integers is the larger of the integers, while the common type of a signed and an unsigned integer is a signed integer that can hold the full ranges of both integers. This avoids messing up the indices, except in the case of std::uint64_t, where there is no fundamental signed type that can hold its entire range. In this case, we choose std::uint64_t as the common type, since the most common use case is where the indices go from 0 to x where x has been previously defined as std::size_t. This will fail if the first index is negative; in that case, the user must cast the indices explicitly.
    • API migration: Existing code which uses type casting or explicit template parameters in parallelization functions does not need to be changed, but it can be simplified by removing the casting or template parameters. However, if one index is negative and the other is an unsigned 64-bit integer, casting is still needed (although you should probably not be doing this in the first place, as casting to either of the two types will result in potential narrowing or overflow).
  • BS::this_thread:
    • BS::this_thread is now a class instead of a namespace, since defining it as a namespace proved to be incompatible with C++20 modules (at least in some compilers). Defining it as a class also results in a simpler implementation. However, the functionality remains the same, and since it only has static methods, the call syntax for BS::this_thread::get_index() and BS::this_thread::get_pool() is unchanged.
    • The type aliases BS::this_thread::optional_index and BS::this_thread::optional_pool have been removed. Instead, BS::this_thread::get_index() now returns the explicit type std::optional<std::size_t>, and BS::this_thread::get_pool() returns std::optional<void*>.
      • The rationale for this removal is that using std::optional explicitly provides more information about the type that is being returned, and most users are probably not using the explicit types anyway (either by using auto or by invoking the std::optional member functions directly on the returned object).
    • Note that BS::this_thread::get_pool() now returns an optional void* instead of BS::thread_pool*. The reason for that is that BS::thread_pool is now a template. Once you obtain the pool pointer, you must cast it to the desired instantiation of the template if you want to use any member functions. Note that you have to cast it to the correct type; if you cast a pointer to a BS::light_thread_pool into a pointer to a BS::priority_thread_pool, for example, your program will have undefined behavior.
    • API migration:
      • If your code uses the type aliases, please replace BS::this_thread::optional_index with std::optional<std::size_t> and BS::this_thread::optional_pool with std::optional<void*>.
      • If your code uses BS::this_thread::get_pool(), you must now cast the returned pointer to the correct instantiation of the BS::thread_pool class template before using any member functions.
  • Determining the library version:
    • The library now defines the constexpr object BS::thread_pool_version, which can be used to check the version of the library at compilation time. This object is of type BS::version, with members major, minor, and patch, and all comparison operators defined as constexpr. It also has a to_string() member function and an operator<< overload for easy printing at runtime. For example, you can do static_assert(BS::thread_pool_version == BS::version(5, 0, 0)), or you can use it in if constexpr for conditional compilation.
    • The version macros BS_THREAD_POOL_VERSION_MAJOR, BS_THREAD_POOL_VERSION_MINOR, and BS_THREAD_POOL_VERSION_PATCH are still defined, since they can be used in conditional code inclusion, and for backwards compatibility. However, since C++20 modules cannot export macros, BS::thread_pool_version is the only way to check the version of the thread pool library if you are importing it as a module.
    • API migration: No changes needed in existing code; if you previously used the macros BS_THREAD_POOL_VERSION_MAJOR, BS_THREAD_POOL_VERSION_MINOR, and BS_THREAD_POOL_VERSION_PATCH to determine the version of the library when including it as a header file, you can still do so. However, if you wish to import the library as a C++20 module, you must use the object BS::thread_pool_version instead.
  • Task priority:
    • The type priority_t, used to set priorities, is now defined as std::int8_t, which means it takes values from -128 to +127. The pre-defined priorities in BS::pr, such as BS::pr::highest or BS::pr::lowest, have been updated accordingly (also, it is now an enum instead of a namespace). The old priority type std::int16_t was unnecessarily large; having fewer priority values means less bookkeeping in the priority queue, which should also improve performance.
    • API migration: If you used the pre-defined priorities in BS::pr, no changes are needed. If you specified numerical priorities directly, you may need to adjust them to the new range of -128 to +127.
  • Miscellaneous:
    • Exceptions thrown by detached tasks are now caught and prevented from propagating, so that they do not terminate the program. Exceptions thrown by submitted tasks are still rethrown when calling get() on the future, as before.
    • All member functions which parallelize collections of tasks, namely detach_blocks(), detach_loop(), detach_sequence(), submit_blocks(), submit_loop(), and submit_sequence(), now store the callable object inside an std::shared_ptr, and then pass that shared pointer to each subtask. Previously, the callable was passed using perfect forwarding, which under some circumstances resulted in mistakenly moving the callable during the first iteration of the loop, thus potentially destructing captured objects prematurely. The new shared pointer method resolves this issue, while also avoiding making copies of the callable. See #149.
    • Fixed incorrect reading of shared variables while the mutex is unlocked in the worker function. See #159.
    • Added documentation to README.md for all the new features. In addition, fixed some typos and other minor issues in the existing documentation.
    • Added instructions in README.md for installing the library using CMake with FetchContent instead of CPM. See #155.
    • The type BS::concurrency_t has been removed. In previous versions this type was defined to be the type of the value returned by std::thread::hardware_concurrency() (which is supposed to be unsigned int), for maximum portability. However, in practice this value is only used to indicate the size of arrays, so std::size_t is more appropriate, and this simplifies the code.
    • API migration: If you used BS::concurrency_t in your code, please replace it with std::size_t. If you previously cast to/from these two types, you can now remove the cast.
  • Tests:
    • The test program BS_thread_pool_test.cpp will import the library as a C++20 module via import BS.thread_pool if the macro BS_THREAD_POOL_TEST_IMPORT_MODULE is defined, C++20 or later is detected, and a supported compiler is used.
    • The test program will also import the C++ Standard Library as a module using import std if the macro BS_THREAD_POOL_IMPORT_STD is defined during compilation, on supported compilers and platforms.
    • The new test check_copy() checks that the callable object does not get copied when parallelized into multiple tasks. It will succeed on previous versions of the library, but not if perfect forwarding is removed.
    • The new test check_shared_ptr() checks that captured shared pointers do not prematurely destruct. It will fail on previous versions.
    • The new test check_task_destruct() checks that a task is destructed immediately after it executes, and therefore does not artificially extend the lifetime of any captured objects.
    • The new test check_common_index_type() checks that the type trait BS::common_index_type (see above) works as expected.
    • The new tests check_os_process_priorities(), check_os_thread_priorities(), check_os_process_affinity(), check_os_thread_affinity(), and check_os_thread_names() check the corresponding features of the native extensions.
    • The new test check_callables() checks that different callable types are accepted by the thread pool.
    • New command line argument: stdout, to print to the standard output, enabled by default.
    • If the file default_args.txt exists in the same folder, the test program reads the default arguments from it (space separated in a single line). Command line arguments can still override these defaults. This is useful when debugging.
    • The test program will now detect and log the OS, compiler, standard library, C++ standard, available C++ features, whether the thread pool was imported as a C++20 module, and whether the standard library was imported as a module.
  • Benchmarks:
    • Added new and improved benchmarks using a highly-optimized multithreaded algorithm which generates a plot of the Mandelbrot set, utilizing a normalized iteration count algorithm and linear interpolation to create smooth coloring.
    • These benchmarks are heavily CPU-intensive, and much less limited by memory and cache compared to the benchmarks in previous versions (which used vector or matrix operations). This results in a much higher speedup factor due to multithreading, utilizing every core and thread to their fullest extent. This makes these benchmarks more useful for optimizing the library, since they are more sensitive to the thread pool's own performance.
    • The full benchmarks are enabled using the command line argument benchmarks, which is enabled by default. The command line argument plot can be used to just plot the Mandelbrot set once, either instead of or in addition to doing the full benchmarks. This will plot the largest possible image that can be plotted in 5 seconds, and only measure the performance in pixels/ms for the entire plot.
    • If you want to see the actual plot, pass the save command line argument. The plot is saved to a BMP file, since I didn't want to depend on any 3rd-party libraries. This is off by default, since that file can get quite large.
  • Development:
    • A Python script compile_cpp.py has been added to the repository, in the scripts folder. It can be used to compile any C++ source file with different compilers on different platforms. The compilation parameters can be configured using command line arguments and/or via an optional YAML configuration file compile_cpp.yaml which specifies defined macros, extra compiler flags (per compiler), include folders, modules, and the output folder.
    • I wrote this script to make it easier for me to test the library with different combinations of compilers, standards, and platforms using the built-in Visual Studio Code tasks. I also included three .vscode folders (one for each OS) in the repository, with appropriate c_cpp_properties.json, launch.json, and tasks.json files that utilize this script, in case you want to use it in your own projects. However, note that this script is not meant to replace CMake or any full-fledged build system, it's just a convenient script for developing single-header libraries like this one or other small projects.
    • The compile_cpp.py script also transparently handles C++20 modules and importing the C++ Standard Library as a module in C++23. Therefore, users of this library who wish to import it as a C+20 module may find this script particularly useful.
    • Another Python script test_all.py in the scripts folder replaces the old PowerShell test script. Tests are now performed in C++17, C++20, and C++23 modes, using all compilers available in the system (Clang, GCC, and/or MSVC). Since there are so many tests, the test script now no longer performs the benchmarks, as that would take too long.
    • A final Python script clear_folder.py in the scripts folder is used to clean up output and temporary folders, and integrates with VS Code tasks.