Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLeitwolf112026-09-29 19:54:42 +0300
committerLeitwolf112026-09-29 19:54:42 +0300
commita870d0a4b37c2840db4eeaaaaf57b4eab4db75d3 (patch)
tree4de83fbe2df34f34160b2dad2a60147bb2a0b1be
parent1f2ec07fdfe05af6df9a573f2c16551e91eef3a6 (diff)
downloadcatch3-a870d0a4b37c2840db4eeaaaaf57b4eab4db75d3.tar.gz
Support exception-free clients in Catch3 property headers
Preserve the existing exception-throwing and exception-catching API when language exceptions are enabled. Compile the property evaluator, shrinker and value formatter without try/catch for clients using -fno-exceptions, and fail loudly on invalid generator range or missing callbacks instead of requiring throw expressions in those translation units. Export the new diagnostic helper through Bazel and Meson header installations. This changes neither the upstream Catch2 implementation nor its compiler flags. Validation: Clang++ C++20 syntax checks passed for property generators with exceptions both disabled and enabled; changed headers were formatted and git diff --check passed. The consuming compiler's Linux/Windows/macOS CI will verify the integration.
-rw-r--r--src/Progmasoft/BUILD.bazel1
-rw-r--r--src/Progmasoft/Catch3/Detail/InvalidArgument.hpp27
-rw-r--r--src/Progmasoft/Catch3/Generator.hpp55
-rw-r--r--src/Progmasoft/Catch3/Generators/Integer.hpp108
-rw-r--r--src/Progmasoft/Catch3/Property.hpp283
-rw-r--r--src/Progmasoft/Catch3/Random.hpp113
-rw-r--r--src/Progmasoft/meson.build1
7 files changed, 368 insertions, 220 deletions
diff --git a/src/Progmasoft/BUILD.bazel b/src/Progmasoft/BUILD.bazel
index 642bc158..ed49083c 100644
--- a/src/Progmasoft/BUILD.bazel
+++ b/src/Progmasoft/BUILD.bazel
@@ -14,6 +14,7 @@ cc_library(
hdrs = [
"Catch3.hpp",
"Catch3/Assertions.hpp",
+ "Catch3/Detail/InvalidArgument.hpp",
"Catch3/Generator.hpp",
"Catch3/Generators/Boolean.hpp",
"Catch3/Generators/Integer.hpp",
diff --git a/src/Progmasoft/Catch3/Detail/InvalidArgument.hpp b/src/Progmasoft/Catch3/Detail/InvalidArgument.hpp
new file mode 100644
index 00000000..382d84d1
--- /dev/null
+++ b/src/Progmasoft/Catch3/Detail/InvalidArgument.hpp
@@ -0,0 +1,27 @@
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+#pragma once
+
+#include <cstdio>
+#include <cstdlib>
+#include <stdexcept>
+
+namespace Progmasoft::Catch3::Detail
+{
+
+ // An invalid generator contract cannot be represented by a value-returning
+ // factory. Preserve the exception API for normal clients and fail loudly
+ // when a caller deliberately compiles without language exceptions.
+ [[noreturn]] static inline void
+ RejectInvalidArgument(const char *message)
+ {
+#if defined(__cpp_exceptions)
+ throw std::invalid_argument(message);
+#else
+ std::fputs(message, stderr);
+ std::fputc('\n', stderr);
+ std::abort();
+#endif
+ }
+
+} // namespace Progmasoft::Catch3::Detail
diff --git a/src/Progmasoft/Catch3/Generator.hpp b/src/Progmasoft/Catch3/Generator.hpp
index 0e48aaa3..4c52fc6f 100644
--- a/src/Progmasoft/Catch3/Generator.hpp
+++ b/src/Progmasoft/Catch3/Generator.hpp
@@ -2,42 +2,50 @@
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
#pragma once
+#include <Progmasoft/Catch3/Detail/InvalidArgument.hpp>
#include <Progmasoft/Catch3/Random.hpp>
-
#include <functional>
#include <stdexcept>
#include <utility>
#include <vector>
-namespace Progmasoft::Catch3 {
+namespace Progmasoft::Catch3
+{
/// A value source paired with an ordered sequence of simpler candidates.
///
/// The property runner owns the shrinking policy; generators only describe
/// which candidates are valid and which order is most useful for
/// minimization.
- template <typename ValueType>
- class Generator final {
+ template<typename ValueType>
+ class Generator final
+ {
public:
- using GenerationFunction = std::function<ValueType( Random& )>;
- using ShrinkingFunction =
- std::function<std::vector<ValueType>( const ValueType& )>;
-
- Generator( GenerationFunction generate, ShrinkingFunction shrink ):
- generate_( std::move( generate ) ), shrink_( std::move( shrink ) ) {
- if ( !generate_ || !shrink_ ) {
- throw std::invalid_argument(
- "a generator requires both callbacks" );
+ using GenerationFunction = std::function<ValueType(Random &)>;
+ using ShrinkingFunction
+ = std::function<std::vector<ValueType>(const ValueType &)>;
+
+ Generator(GenerationFunction generate, ShrinkingFunction shrink)
+ : generate_(std::move(generate))
+ , shrink_(std::move(shrink))
+ {
+ if (!generate_ || !shrink_)
+ {
+ Detail::RejectInvalidArgument(
+ "a generator requires both callbacks");
}
}
- [[nodiscard]] ValueType Generate( Random& random ) const {
- return generate_( random );
+ [[nodiscard]] ValueType
+ Generate(Random &random) const
+ {
+ return generate_(random);
}
[[nodiscard]] std::vector<ValueType>
- Shrink( const ValueType& value ) const {
- return shrink_( value );
+ Shrink(const ValueType &value) const
+ {
+ return shrink_(value);
}
private:
@@ -46,16 +54,17 @@ namespace Progmasoft::Catch3 {
};
/// Adapts user callables into a reusable generator.
- template <typename ValueType,
- typename GenerateFunction,
- typename ShrinkFunction>
+ template<typename ValueType,
+ typename GenerateFunction,
+ typename ShrinkFunction>
[[nodiscard]] Generator<ValueType>
- MakeGenerator( GenerateFunction&& generate, ShrinkFunction&& shrink ) {
+ MakeGenerator(GenerateFunction &&generate, ShrinkFunction &&shrink)
+ {
return Generator<ValueType>(
typename Generator<ValueType>::GenerationFunction(
- std::forward<GenerateFunction>( generate ) ),
+ std::forward<GenerateFunction>(generate)),
typename Generator<ValueType>::ShrinkingFunction(
- std::forward<ShrinkFunction>( shrink ) ) );
+ std::forward<ShrinkFunction>(shrink)));
}
} // namespace Progmasoft::Catch3
diff --git a/src/Progmasoft/Catch3/Generators/Integer.hpp b/src/Progmasoft/Catch3/Generators/Integer.hpp
index 2a50525e..45192b3d 100644
--- a/src/Progmasoft/Catch3/Generators/Integer.hpp
+++ b/src/Progmasoft/Catch3/Generators/Integer.hpp
@@ -3,55 +3,81 @@
#pragma once
#include <Progmasoft/Catch3/Generator.hpp>
-
#include <algorithm>
#include <concepts>
#include <limits>
#include <type_traits>
-namespace Progmasoft::Catch3 {
- namespace Detail {
+namespace Progmasoft::Catch3
+{
+ namespace Detail
+ {
- template <std::integral IntegerType>
- requires( !std::same_as<IntegerType, bool> )
- [[nodiscard]] std::vector<IntegerType> ShrinkInteger(
- IntegerType value, IntegerType minimum, IntegerType maximum ) {
+ template<std::integral IntegerType>
+ requires(!std::same_as<IntegerType, bool>)
+ [[nodiscard]] std::vector<IntegerType>
+ ShrinkInteger(IntegerType value,
+ IntegerType minimum,
+ IntegerType maximum)
+ {
std::vector<IntegerType> candidates;
- const auto addCandidate = [&]( IntegerType candidate ) {
- if ( candidate < minimum || candidate > maximum ||
- candidate == value ||
- std::ranges::find( candidates, candidate ) !=
- candidates.end() ) {
+ const auto addCandidate = [&](IntegerType candidate) {
+ if (candidate < minimum || candidate > maximum
+ || candidate == value
+ || std::ranges::find(candidates, candidate)
+ != candidates.end())
+ {
return;
}
- candidates.push_back( candidate );
+ candidates.push_back(candidate);
};
- if ( value == 0 ) { return candidates; }
+ if (value == 0)
+ {
+ return candidates;
+ }
// First try the conventional simplest value, then halve toward it.
// The adjacent value at the end lets the runner find boundary
// counterexamples (for example, the first failing integer in a
// threshold property).
- if ( minimum <= 0 && maximum >= 0 ) { addCandidate( 0 ); }
+ if (minimum <= 0 && maximum >= 0)
+ {
+ addCandidate(0);
+ }
IntegerType smaller = value;
- while ( smaller != 0 ) {
- smaller = static_cast<IntegerType>( smaller / 2 );
- if ( smaller == 0 ) { break; }
- addCandidate( smaller );
+ while (smaller != 0)
+ {
+ smaller = static_cast<IntegerType>(smaller / 2);
+ if (smaller == 0)
+ {
+ break;
+ }
+ addCandidate(smaller);
}
- if ( value > 0 ) {
- if ( value > minimum ) {
- addCandidate( static_cast<IntegerType>( value - 1 ) );
+ if (value > 0)
+ {
+ if (value > minimum)
+ {
+ addCandidate(static_cast<IntegerType>(value - 1));
+ }
+ if (minimum > 0)
+ {
+ addCandidate(minimum);
+ }
+ }
+ else
+ {
+ if (value < maximum)
+ {
+ addCandidate(static_cast<IntegerType>(value + 1));
}
- if ( minimum > 0 ) { addCandidate( minimum ); }
- } else {
- if ( value < maximum ) {
- addCandidate( static_cast<IntegerType>( value + 1 ) );
+ if (maximum < 0)
+ {
+ addCandidate(maximum);
}
- if ( maximum < 0 ) { addCandidate( maximum ); }
}
return candidates;
}
@@ -60,23 +86,25 @@ namespace Progmasoft::Catch3 {
/// Generates integers over the inclusive range and shrinks toward simple
/// values.
- template <std::integral IntegerType>
- requires( !std::same_as<IntegerType, bool> )
- [[nodiscard]] Generator<IntegerType> Integer(
- IntegerType minimum = std::numeric_limits<IntegerType>::lowest(),
- IntegerType maximum = std::numeric_limits<IntegerType>::max() ) {
- if ( minimum > maximum ) {
- throw std::invalid_argument(
- "Integer requires minimum <= maximum" );
+ template<std::integral IntegerType>
+ requires(!std::same_as<IntegerType, bool>)
+ [[nodiscard]] Generator<IntegerType>
+ Integer(IntegerType minimum = std::numeric_limits<IntegerType>::lowest(),
+ IntegerType maximum = std::numeric_limits<IntegerType>::max())
+ {
+ if (minimum > maximum)
+ {
+ Detail::RejectInvalidArgument(
+ "Integer requires minimum <= maximum");
}
return MakeGenerator<IntegerType>(
- [minimum, maximum]( Random& random ) {
- return random.Between( minimum, maximum );
+ [minimum, maximum](Random &random) {
+ return random.Between(minimum, maximum);
},
- [minimum, maximum]( const IntegerType& value ) {
- return Detail::ShrinkInteger( value, minimum, maximum );
- } );
+ [minimum, maximum](const IntegerType &value) {
+ return Detail::ShrinkInteger(value, minimum, maximum);
+ });
}
} // namespace Progmasoft::Catch3
diff --git a/src/Progmasoft/Catch3/Property.hpp b/src/Progmasoft/Catch3/Property.hpp
index 57e74408..1a853bf1 100644
--- a/src/Progmasoft/Catch3/Property.hpp
+++ b/src/Progmasoft/Catch3/Property.hpp
@@ -2,10 +2,8 @@
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
#pragma once
-#include <catch2/catch_tostring.hpp>
-
#include <Progmasoft/Catch3/Generator.hpp>
-
+#include <catch2/catch_tostring.hpp>
#include <concepts>
#include <cstddef>
#include <cstdint>
@@ -17,23 +15,27 @@
#include <type_traits>
#include <utility>
-namespace Progmasoft::Catch3 {
+namespace Progmasoft::Catch3
+{
/// Controls a property run without changing the compatibility runner's CLI.
- struct PropertyOptions final {
+ struct PropertyOptions final
+ {
std::size_t Trials = 100;
std::uint64_t Seed = 0xC47C4A53ULL;
std::size_t MaxShrinkSteps = 1000;
};
- enum class PropertyStatus : std::uint8_t {
+ enum class PropertyStatus : std::uint8_t
+ {
kPassed,
kFailed,
kErrored,
kInvalidConfiguration,
};
- struct PropertyFailure final {
+ struct PropertyFailure final
+ {
std::uint64_t RootSeed = 0;
std::uint64_t TrialSeed = 0;
std::size_t Trial = 0;
@@ -44,35 +46,43 @@ namespace Progmasoft::Catch3 {
/// Stable, machine-readable outcome with a deterministic human-readable
/// report.
- struct PropertyResult final {
+ struct PropertyResult final
+ {
PropertyStatus Status = PropertyStatus::kPassed;
std::size_t TrialsRun = 0;
std::optional<PropertyFailure> Failure;
std::string Error;
- [[nodiscard]] bool Succeeded() const noexcept {
+ [[nodiscard]] bool
+ Succeeded() const noexcept
+ {
return Status == PropertyStatus::kPassed;
}
- [[nodiscard]] std::string Describe() const {
+ [[nodiscard]] std::string
+ Describe() const
+ {
std::ostringstream output;
- switch ( Status ) {
- case PropertyStatus::kPassed:
- output << "property passed: " << TrialsRun << " trials";
- return output.str();
- case PropertyStatus::kInvalidConfiguration:
- output << "invalid property configuration: " << Error;
- return output.str();
- case PropertyStatus::kErrored:
- output << "property execution errored after " << TrialsRun
- << " trials: " << Error;
- break;
- case PropertyStatus::kFailed:
- output << "property failed after " << TrialsRun << " trials";
- break;
+ switch (Status)
+ {
+ case PropertyStatus::kPassed:
+ output << "property passed: " << TrialsRun << " trials";
+ return output.str();
+ case PropertyStatus::kInvalidConfiguration:
+ output << "invalid property configuration: " << Error;
+ return output.str();
+ case PropertyStatus::kErrored:
+ output << "property execution errored after " << TrialsRun
+ << " trials: " << Error;
+ break;
+ case PropertyStatus::kFailed:
+ output << "property failed after " << TrialsRun
+ << " trials";
+ break;
}
- if ( Failure.has_value() ) {
+ if (Failure.has_value())
+ {
output << "; failing trial " << Failure->Trial << ", root seed "
<< Failure->RootSeed << ", replay seed "
<< Failure->TrialSeed << ", shrink checks "
@@ -84,56 +94,83 @@ namespace Progmasoft::Catch3 {
}
};
- namespace Detail {
+ namespace Detail
+ {
- template <typename ValueType>
+ template<typename ValueType>
[[nodiscard]] std::string
- StringifyPropertyValue( const ValueType& value ) {
+ StringifyPropertyValue(const ValueType &value)
+ {
return Catch::StringMaker<std::remove_cvref_t<ValueType>>::convert(
- value );
+ value);
}
- template <typename Predicate, typename ValueType>
+ template<typename Predicate, typename ValueType>
[[nodiscard]] std::optional<std::string>
- EvaluateProperty( Predicate& predicate, const ValueType& value ) {
- try {
- if ( static_cast<bool>( std::invoke( predicate, value ) ) ) {
+ EvaluateProperty(Predicate &predicate, const ValueType &value)
+ {
+#if defined(__cpp_exceptions)
+ try
+ {
+#endif
+ if (static_cast<bool>(std::invoke(predicate, value)))
+ {
return std::nullopt;
}
return "predicate returned false";
- } catch ( const std::exception& exception ) {
- return std::string( "predicate threw std::exception: " ) +
- exception.what();
- } catch ( ... ) {
+#if defined(__cpp_exceptions)
+ }
+ catch (const std::exception &exception)
+ {
+ return std::string("predicate threw std::exception: ")
+ + exception.what();
+ }
+ catch (...)
+ {
return "predicate threw a non-standard exception";
}
+#endif
}
- template <typename ValueType>
+ template<typename ValueType>
[[nodiscard]] std::string
- DescribeValue( const ValueType& value ) noexcept {
- try {
- return StringifyPropertyValue( value );
- } catch ( const std::exception& exception ) {
- return std::string( "<stringification failed: " ) +
- exception.what() + ">";
- } catch ( ... ) {
+ DescribeValue(const ValueType &value) noexcept
+ {
+#if defined(__cpp_exceptions)
+ try
+ {
+#endif
+ return StringifyPropertyValue(value);
+#if defined(__cpp_exceptions)
+ }
+ catch (const std::exception &exception)
+ {
+ return std::string("<stringification failed: ")
+ + exception.what() + ">";
+ }
+ catch (...)
+ {
return "<stringification failed with a non-standard exception>";
}
+#endif
}
} // namespace Detail
/// Runs independently seeded trials and greedily accepts failing shrink
/// candidates.
- template <typename ValueType, typename Predicate>
- requires std::invocable<Predicate&, const ValueType&>&&
- std::convertible_to<std::invoke_result_t<Predicate&, const ValueType&>,
- bool> [[nodiscard]] PropertyResult
- CheckProperty( const Generator<ValueType>& generator,
- Predicate predicate,
- PropertyOptions options = {} ) {
- if ( options.Trials == 0 ) {
+ template<typename ValueType, typename Predicate>
+ requires std::invocable<Predicate &, const ValueType &>
+ && std::convertible_to<
+ std::invoke_result_t<Predicate &, const ValueType &>,
+ bool>
+ [[nodiscard]] PropertyResult
+ CheckProperty(const Generator<ValueType> &generator,
+ Predicate predicate,
+ PropertyOptions options = {})
+ {
+ if (options.Trials == 0)
+ {
return { PropertyStatus::kInvalidConfiguration,
0,
std::nullopt,
@@ -141,98 +178,120 @@ namespace Progmasoft::Catch3 {
}
PropertyResult result;
- for ( std::size_t trial = 0; trial < options.Trials; ++trial ) {
+ for (std::size_t trial = 0; trial < options.Trials; ++trial)
+ {
result.TrialsRun = trial + 1;
- const std::uint64_t trialSeed =
- Random::DeriveSeed( options.Seed, trial );
- Random random( trialSeed );
+ const std::uint64_t trialSeed
+ = Random::DeriveSeed(options.Seed, trial);
+ Random random(trialSeed);
std::optional<ValueType> sample;
- try {
- sample.emplace( generator.Generate( random ) );
- } catch ( const std::exception& exception ) {
+#if defined(__cpp_exceptions)
+ try
+ {
+#endif
+ sample.emplace(generator.Generate(random));
+#if defined(__cpp_exceptions)
+ }
+ catch (const std::exception &exception)
+ {
result.Status = PropertyStatus::kErrored;
- result.Error =
- std::string( "generator threw std::exception: " ) +
- exception.what();
- result.Failure = PropertyFailure{ options.Seed,
- trialSeed,
- trial + 1,
- 0,
- "<not generated>",
- result.Error };
+ result.Error = std::string("generator threw std::exception: ")
+ + exception.what();
+ result.Failure
+ = PropertyFailure{ options.Seed, trialSeed,
+ trial + 1, 0,
+ "<not generated>", result.Error };
return result;
- } catch ( ... ) {
+ }
+ catch (...)
+ {
result.Status = PropertyStatus::kErrored;
result.Error = "generator threw a non-standard exception";
- result.Failure = PropertyFailure{ options.Seed,
- trialSeed,
- trial + 1,
- 0,
- "<not generated>",
- result.Error };
+ result.Failure
+ = PropertyFailure{ options.Seed, trialSeed,
+ trial + 1, 0,
+ "<not generated>", result.Error };
return result;
}
+#endif
- auto reason = Detail::EvaluateProperty( predicate, *sample );
- if ( !reason.has_value() ) { continue; }
+ auto reason = Detail::EvaluateProperty(predicate, *sample);
+ if (!reason.has_value())
+ {
+ continue;
+ }
std::size_t shrinkChecks = 0;
- try {
+#if defined(__cpp_exceptions)
+ try
+ {
+#endif
bool improved = true;
- while ( improved && shrinkChecks < options.MaxShrinkSteps ) {
+ while (improved && shrinkChecks < options.MaxShrinkSteps)
+ {
improved = false;
- const std::vector<ValueType> candidates =
- generator.Shrink( *sample );
- for ( std::size_t candidateIndex = 0;
- candidateIndex < candidates.size();
- ++candidateIndex ) {
- if ( shrinkChecks >= options.MaxShrinkSteps ) { break; }
+ const std::vector<ValueType> candidates
+ = generator.Shrink(*sample);
+ for (std::size_t candidateIndex = 0;
+ candidateIndex < candidates.size();
+ ++candidateIndex)
+ {
+ if (shrinkChecks >= options.MaxShrinkSteps)
+ {
+ break;
+ }
++shrinkChecks;
const ValueType candidate = static_cast<ValueType>(
- candidates[candidateIndex] );
- auto candidateReason =
- Detail::EvaluateProperty( predicate, candidate );
- if ( candidateReason.has_value() ) {
- sample.emplace( candidate );
- reason = std::move( candidateReason );
+ candidates[candidateIndex]);
+ auto candidateReason
+ = Detail::EvaluateProperty(predicate, candidate);
+ if (candidateReason.has_value())
+ {
+ sample.emplace(candidate);
+ reason = std::move(candidateReason);
improved = true;
break;
}
}
}
- } catch ( const std::exception& exception ) {
+#if defined(__cpp_exceptions)
+ }
+ catch (const std::exception &exception)
+ {
result.Status = PropertyStatus::kErrored;
- result.Error =
- std::string( "shrinker threw std::exception: " ) +
- exception.what();
- result.Failure =
- PropertyFailure{ options.Seed,
- trialSeed,
- trial + 1,
- shrinkChecks,
- Detail::DescribeValue( *sample ),
- *reason };
+ result.Error = std::string("shrinker threw std::exception: ")
+ + exception.what();
+ result.Failure
+ = PropertyFailure{ options.Seed,
+ trialSeed,
+ trial + 1,
+ shrinkChecks,
+ Detail::DescribeValue(*sample),
+ *reason };
return result;
- } catch ( ... ) {
+ }
+ catch (...)
+ {
result.Status = PropertyStatus::kErrored;
result.Error = "shrinker threw a non-standard exception";
- result.Failure =
- PropertyFailure{ options.Seed,
- trialSeed,
- trial + 1,
- shrinkChecks,
- Detail::DescribeValue( *sample ),
- *reason };
+ result.Failure
+ = PropertyFailure{ options.Seed,
+ trialSeed,
+ trial + 1,
+ shrinkChecks,
+ Detail::DescribeValue(*sample),
+ *reason };
return result;
}
+#endif
result.Status = PropertyStatus::kFailed;
result.Failure = PropertyFailure{ options.Seed,
trialSeed,
trial + 1,
shrinkChecks,
- Detail::DescribeValue( *sample ),
+ Detail::DescribeValue(*sample),
*reason };
return result;
}
diff --git a/src/Progmasoft/Catch3/Random.hpp b/src/Progmasoft/Catch3/Random.hpp
index 0a95cb84..a1f1ac94 100644
--- a/src/Progmasoft/Catch3/Random.hpp
+++ b/src/Progmasoft/Catch3/Random.hpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
#pragma once
+#include <Progmasoft/Catch3/Detail/InvalidArgument.hpp>
#include <bit>
#include <concepts>
#include <cstdint>
@@ -9,7 +10,8 @@
#include <stdexcept>
#include <type_traits>
-namespace Progmasoft::Catch3 {
+namespace Progmasoft::Catch3
+{
/// Small, deterministic random source for repeatable property-test cases.
///
@@ -18,87 +20,108 @@ namespace Progmasoft::Catch3 {
/// library implementations. A Random object is intentionally not
/// thread-safe; property trials receive independent instances derived from
/// the root seed.
- class Random final {
+ class Random final
+ {
public:
- explicit constexpr Random( std::uint64_t seed ) noexcept:
- state_( seed ) {}
+ explicit constexpr Random(std::uint64_t seed) noexcept
+ : state_(seed)
+ {}
/// Returns the next value in the stable SplitMix64 sequence.
- [[nodiscard]] constexpr std::uint64_t Next() noexcept {
+ [[nodiscard]] constexpr std::uint64_t
+ Next() noexcept
+ {
state_ += kGamma;
std::uint64_t value = state_;
- value = ( value ^ ( value >> 30U ) ) * kMultiplierOne;
- value = ( value ^ ( value >> 27U ) ) * kMultiplierTwo;
- return value ^ ( value >> 31U );
+ value = (value ^ (value >> 30U)) * kMultiplierOne;
+ value = (value ^ (value >> 27U)) * kMultiplierTwo;
+ return value ^ (value >> 31U);
}
/// Returns a value in [0, bound); bound == 0 requests the full uint64
/// range.
[[nodiscard]] constexpr std::uint64_t
- Below( std::uint64_t bound ) noexcept {
- if ( bound == 0 ) { return Next(); }
+ Below(std::uint64_t bound) noexcept
+ {
+ if (bound == 0)
+ {
+ return Next();
+ }
// Rejection sampling avoids the modulo bias of a single `% bound`.
- const std::uint64_t threshold =
- ( std::uint64_t{ 0 } - bound ) % bound;
- while ( true ) {
+ const std::uint64_t threshold
+ = (std::uint64_t{ 0 } - bound) % bound;
+ while (true)
+ {
const std::uint64_t value = Next();
- if ( value >= threshold ) { return value % bound; }
+ if (value >= threshold)
+ {
+ return value % bound;
+ }
}
}
/// Returns an inclusive integral value without overflowing at type
/// limits.
- template <std::integral IntegerType>
- requires( !std::same_as<IntegerType, bool> ) [[nodiscard]] IntegerType
- Between( IntegerType minimum, IntegerType maximum ) {
- if ( minimum > maximum ) {
- throw std::invalid_argument(
- "Random::Between requires minimum <= maximum" );
+ template<std::integral IntegerType>
+ requires(!std::same_as<IntegerType, bool>)
+ [[nodiscard]] IntegerType
+ Between(IntegerType minimum, IntegerType maximum)
+ {
+ if (minimum > maximum)
+ {
+ Detail::RejectInvalidArgument(
+ "Random::Between requires minimum <= maximum");
}
using UnsignedType = std::make_unsigned_t<IntegerType>;
- static_assert( std::numeric_limits<UnsignedType>::digits <= 64 );
+ static_assert(std::numeric_limits<UnsignedType>::digits <= 64);
constexpr unsigned kBitCount = static_cast<unsigned>(
- std::numeric_limits<UnsignedType>::digits );
+ std::numeric_limits<UnsignedType>::digits);
constexpr UnsignedType kSignBit = UnsignedType{ 1 }
- << ( kBitCount - 1U );
+ << (kBitCount - 1U);
- const auto encode =
- []( IntegerType value ) constexpr -> UnsignedType {
- if constexpr ( std::is_signed_v<IntegerType> ) {
- return std::bit_cast<UnsignedType>( value ) ^ kSignBit;
- } else {
+ const auto encode
+ = [](IntegerType value) constexpr -> UnsignedType {
+ if constexpr (std::is_signed_v<IntegerType>)
+ {
+ return std::bit_cast<UnsignedType>(value) ^ kSignBit;
+ }
+ else
+ {
return value;
}
};
- const auto decode =
- []( UnsignedType value ) constexpr -> IntegerType {
-