// Copyright (c) 2026 Progmasoft. // SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1 #pragma once #include #include #include #include #include namespace Progmasoft::Catch3 { namespace Detail { template void AppendComponentCandidates( const std::tuple...> &generators, const std::tuple &value, std::vector> &candidates) { for (const auto &simpler : std::get(generators).Shrink(std::get(value))) { std::tuple replaced = value; std::get(replaced) = simpler; candidates.push_back(std::move(replaced)); } } template [[nodiscard]] std::vector> ShrinkTuple(const std::tuple...> &generators, const std::tuple &value, std::index_sequence) { std::vector> candidates; // The comma fold visits components strictly left to right. (AppendComponentCandidates(generators, value, candidates), ...); return candidates; } } // namespace Detail /// Combines independent generators into one generator of tuples, which /// is how a property receives several generated arguments. /// /// Components are drawn strictly left to right from the trial's random /// source, so a tuple is reproducible from its replay seed. Shrinking /// changes one component at a time: all candidates of the first /// component, then all of the second, and so on. Combined with the /// runner's greedy restart this minimizes every component without /// trying the cross product of candidates. template [[nodiscard]] Generator> Tuple(Generator... generators) { static_assert(sizeof...(ValueTypes) > 0, "Tuple requires at least one generator"); using Value = std::tuple; const std::tuple...> components{ std::move( generators)... }; return MakeGenerator( [components](Random &random) { return std::apply( [&random](const Generator &...component) { // Braced initialization fixes the evaluation order; // a function-call argument list would not. return Value{ component.Generate(random)... }; }, components); }, [components](const Value &value) { return Detail::ShrinkTuple( components, value, std::index_sequence_for{}); }); } } // namespace Progmasoft::Catch3