From 165d5fbbcd1ca0bc5d2be6ff642ce69d6fb29c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Devaucoup?= Date: Fri, 5 Dec 2025 22:17:19 +0100 Subject: [PATCH 1/5] Add TupleGenerator and tuple_as_range --- .../generators/catch_generators_tuple.hpp | 124 ++++++++++++++++++ .../SelfTest/UsageTests/Generators.tests.cpp | 36 +++++ 2 files changed, 160 insertions(+) create mode 100644 src/catch2/generators/catch_generators_tuple.hpp diff --git a/src/catch2/generators/catch_generators_tuple.hpp b/src/catch2/generators/catch_generators_tuple.hpp new file mode 100644 index 0000000000..bf54149f75 --- /dev/null +++ b/src/catch2/generators/catch_generators_tuple.hpp @@ -0,0 +1,124 @@ +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_GENERATORS_TUPLE_HPINCLUDED +#define CATCH_GENERATORS_TUPLE_HPINCLUDED + +#include + +#include +#include +#include + +namespace Catch { + namespace Generators { + namespace Detail { + + template + struct TupleRuntimeAccessTable { + template + static constexpr Ret access_tuple( Tup& tuple, Fun& fun ) { + return fun( std::get( tuple ) ); + } + + using AccessorFunPtr = Ret ( * )( Tup&, Fun& ); + static constexpr std::size_t table_size{ sizeof...( Idxs ) }; + + static constexpr std::array + lookup_table{ { access_tuple... } }; + }; + + template + constexpr auto + call_access_function( Tup& tuple, + std::size_t index, + Fun fun, + std::index_sequence ) { + using FirstTupleIndexType = decltype( std::get<0>( tuple ) ); + using FunReturnType = + std::invoke_result_t; + + constexpr auto& table{ + TupleRuntimeAccessTable:: + lookup_table }; + return table[index]( tuple, fun ); + } + + template + constexpr auto + runtime_get( Tup& tuple, std::size_t index, Fun fun ) { + return call_access_function( + tuple, + index, + CATCH_FORWARD( fun ), + std::make_index_sequence>{} ); + } + + template + class TupleAccessor { + Tup m_tuple; + std::size_t m_current_index; + + public: + template + constexpr TupleAccessor( Args&&... args ): + m_tuple{ CATCH_FORWARD( args )... }, m_current_index{ 0 } {} + + constexpr TupleAccessor& operator++() { + ++m_current_index; + return *this; + } + + constexpr operator bool() const { + return m_current_index < std::tuple_size_v; + } + + template + constexpr auto perform( Fun fun ) const { + return runtime_get( + m_tuple, m_current_index, CATCH_FORWARD( fun ) ); + } + }; + + } // namespace Detail + + template + class TupleGenerator final + : public IGenerator> { + Detail::TupleAccessor m_iterator; + + public: + template + TupleGenerator( Args&&... args ): + m_iterator{ CATCH_FORWARD( args )... } {} + + const Detail::TupleAccessor& get() const override { + return m_iterator; + } + + bool next() override { return ++m_iterator; } + }; + + template + auto tuple_as_range( Args&&... args ) { + return GeneratorWrapper>( + Catch::Detail::make_unique>( + CATCH_FORWARD( args )... ) ); + } + + template + auto tuple_as_range( Args&&... args ) { + return tuple_as_range>( + CATCH_FORWARD( args )... ); + } + + } // namespace Generators +} // namespace Catch + +#endif // CATCH_GENERATORS_TUPLE_HPINCLUDED diff --git a/tests/SelfTest/UsageTests/Generators.tests.cpp b/tests/SelfTest/UsageTests/Generators.tests.cpp index f04cf4f099..de705fb6d6 100644 --- a/tests/SelfTest/UsageTests/Generators.tests.cpp +++ b/tests/SelfTest/UsageTests/Generators.tests.cpp @@ -7,12 +7,15 @@ // SPDX-License-Identifier: BSL-1.0 #include +#include #include #include #include #include +#include #include +#include // Generators and sections can be nested freely @@ -321,3 +324,36 @@ TEST_CASE( "GENERATE can combine literals and generators", "[generators]" ) { random( -100, 100 ) ) ) ); REQUIRE( i % 2 == 0 ); } + +TEST_CASE( "Tuple", "[generators]" ) { + // imagine two different serialization to test + // once a serialization is tested, the second must be identical + static const auto std_serialization{ []( const auto& element ) { + std::stringstream ss; + ss << element; + return ss.str(); + } }; + static const auto catch2_serialization{ []( const auto& element ) { + return Catch::StringMaker::convert( element ); + } }; + + int counter{ 0 }; + + const auto accessor = GENERATE( tuple_as_range( 42, "foo", 3.14 ) ); + accessor.perform( [&]( const auto& element ) { + REQUIRE( std_serialization( element ) == + catch2_serialization( element ) ); + ++counter; + } ); + + // also work with std::pair or a tuple-like + const auto accessor_pair = + GENERATE( tuple_as_range>( 42, "foo" ) ); + accessor_pair.perform( [&]( const auto& element ) { + REQUIRE( std_serialization( element ) == + catch2_serialization( element ) ); + ++counter; + } ); + + REQUIRE( counter == 2 ); +} From d26285d3422c81ae9531f72cca18a3ee5d8565a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Devaucoup?= Date: Fri, 5 Dec 2025 22:17:19 +0100 Subject: [PATCH 2/5] Fix c++14 compilation --- .../generators/catch_generators_tuple.hpp | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/catch2/generators/catch_generators_tuple.hpp b/src/catch2/generators/catch_generators_tuple.hpp index bf54149f75..8c202c8b82 100644 --- a/src/catch2/generators/catch_generators_tuple.hpp +++ b/src/catch2/generators/catch_generators_tuple.hpp @@ -16,23 +16,21 @@ namespace Catch { namespace Generators { namespace Detail { + template + constexpr Ret access_tuple( Tup& tuple, Fun& fun ) { + return fun( std::get( tuple ) ); + } template - struct TupleRuntimeAccessTable { - template - static constexpr Ret access_tuple( Tup& tuple, Fun& fun ) { - return fun( std::get( tuple ) ); - } - + constexpr auto tuple_runtime_access_table() { using AccessorFunPtr = Ret ( * )( Tup&, Fun& ); - static constexpr std::size_t table_size{ sizeof...( Idxs ) }; - - static constexpr std::array - lookup_table{ { access_tuple... } }; - }; + constexpr std::size_t table_size{ sizeof...( Idxs ) }; + return std::array{ + { access_tuple... } }; + } template constexpr auto @@ -42,11 +40,16 @@ namespace Catch { std::index_sequence ) { using FirstTupleIndexType = decltype( std::get<0>( tuple ) ); using FunReturnType = +#ifdef __cpp_lib_is_invocable // C++ >= 17 std::invoke_result_t; - - constexpr auto& table{ - TupleRuntimeAccessTable:: - lookup_table }; +#else + std::result_of_t; +#endif + + constexpr auto table{ tuple_runtime_access_table() }; return table[index]( tuple, fun ); } @@ -57,7 +60,7 @@ namespace Catch { tuple, index, CATCH_FORWARD( fun ), - std::make_index_sequence>{} ); + std::make_index_sequence::value>{} ); } template @@ -76,7 +79,7 @@ namespace Catch { } constexpr operator bool() const { - return m_current_index < std::tuple_size_v; + return m_current_index < std::tuple_size::value; } template From 2b27f4a25f4e6474bc2a3654c21b3cc44c6ce78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Devaucoup?= Date: Wed, 7 Jan 2026 23:02:12 +0100 Subject: [PATCH 3/5] FIx tuple parameter in tuple_as_range --- .../generators/catch_generators_tuple.hpp | 85 ++++++++++----- .../SelfTest/UsageTests/Generators.tests.cpp | 100 ++++++++++++------ 2 files changed, 128 insertions(+), 57 deletions(-) diff --git a/src/catch2/generators/catch_generators_tuple.hpp b/src/catch2/generators/catch_generators_tuple.hpp index 8c202c8b82..6d27673833 100644 --- a/src/catch2/generators/catch_generators_tuple.hpp +++ b/src/catch2/generators/catch_generators_tuple.hpp @@ -16,25 +16,39 @@ namespace Catch { namespace Generators { namespace Detail { - template - constexpr Ret access_tuple( Tup& tuple, Fun& fun ) { + + template + struct is_tuple_like : std::false_type {}; + + template + struct is_tuple_like< + T, + std::void_t::value ), + decltype( std::get<0>( std::declval() ) )>> + : std::true_type {}; + + template + constexpr Ret access_tuple( TupleType& tuple, Fun& fun ) { return fun( std::get( tuple ) ); } template constexpr auto tuple_runtime_access_table() { - using AccessorFunPtr = Ret ( * )( Tup&, Fun& ); + using AccessorFunPtr = Ret ( * )( TupleType&, Fun& ); constexpr std::size_t table_size{ sizeof...( Idxs ) }; return std::array{ - { access_tuple... } }; + { access_tuple... } }; } - template + template constexpr auto - call_access_function( Tup& tuple, + call_access_function( TupleType& tuple, std::size_t index, Fun fun, std::index_sequence ) { @@ -47,39 +61,40 @@ namespace Catch { #endif constexpr auto table{ tuple_runtime_access_table() }; return table[index]( tuple, fun ); } - template + template constexpr auto - runtime_get( Tup& tuple, std::size_t index, Fun fun ) { + runtime_get( TupleType& tuple, std::size_t index, Fun fun ) { return call_access_function( tuple, index, CATCH_FORWARD( fun ), - std::make_index_sequence::value>{} ); + std::make_index_sequence< + std::tuple_size::value>{} ); } - template + template class TupleAccessor { - Tup m_tuple; + TupleType m_tuple; std::size_t m_current_index; public: + template + constexpr TupleAccessor( TupleLike&& tuple ): + m_tuple{ CATCH_FORWARD( tuple ) }, m_current_index{ 0 } {} + template constexpr TupleAccessor( Args&&... args ): m_tuple{ CATCH_FORWARD( args )... }, m_current_index{ 0 } {} - constexpr TupleAccessor& operator++() { + constexpr bool next() { ++m_current_index; - return *this; - } - - constexpr operator bool() const { - return m_current_index < std::tuple_size::value; + return m_current_index < std::tuple_size::value; } template @@ -91,30 +106,46 @@ namespace Catch { } // namespace Detail - template + template class TupleGenerator final - : public IGenerator> { - Detail::TupleAccessor m_iterator; + : public IGenerator> { + Detail::TupleAccessor m_iterator; public: + template + TupleGenerator( TupleLike&& tuple ): + m_iterator{ CATCH_FORWARD( tuple ) } {} + template TupleGenerator( Args&&... args ): m_iterator{ CATCH_FORWARD( args )... } {} - const Detail::TupleAccessor& get() const override { + const Detail::TupleAccessor& get() const override { return m_iterator; } - bool next() override { return ++m_iterator; } + bool next() override { return m_iterator.next(); } }; - template + template ::value>> auto tuple_as_range( Args&&... args ) { - return GeneratorWrapper>( - Catch::Detail::make_unique>( + return GeneratorWrapper>( + Catch::Detail::make_unique>( CATCH_FORWARD( args )... ) ); } + template >, + typename = + std::enable_if_t::value>> + auto tuple_as_range( TupleLike&& tuple ) { + return tuple_as_range( CATCH_FORWARD( tuple ) ); + } + template auto tuple_as_range( Args&&... args ) { return tuple_as_range>( diff --git a/tests/SelfTest/UsageTests/Generators.tests.cpp b/tests/SelfTest/UsageTests/Generators.tests.cpp index de705fb6d6..cbe47e1e60 100644 --- a/tests/SelfTest/UsageTests/Generators.tests.cpp +++ b/tests/SelfTest/UsageTests/Generators.tests.cpp @@ -326,34 +326,74 @@ TEST_CASE( "GENERATE can combine literals and generators", "[generators]" ) { } TEST_CASE( "Tuple", "[generators]" ) { - // imagine two different serialization to test - // once a serialization is tested, the second must be identical - static const auto std_serialization{ []( const auto& element ) { - std::stringstream ss; - ss << element; - return ss.str(); - } }; - static const auto catch2_serialization{ []( const auto& element ) { - return Catch::StringMaker::convert( element ); - } }; - - int counter{ 0 }; - - const auto accessor = GENERATE( tuple_as_range( 42, "foo", 3.14 ) ); - accessor.perform( [&]( const auto& element ) { - REQUIRE( std_serialization( element ) == - catch2_serialization( element ) ); - ++counter; - } ); - - // also work with std::pair or a tuple-like - const auto accessor_pair = - GENERATE( tuple_as_range>( 42, "foo" ) ); - accessor_pair.perform( [&]( const auto& element ) { - REQUIRE( std_serialization( element ) == - catch2_serialization( element ) ); - ++counter; - } ); - - REQUIRE( counter == 2 ); + SECTION( "Basic Use Case" ) { + // imagine two different serialization to test + // once a serialization is tested, the second must be identical + static const auto std_serialization{ []( const auto& element ) { + std::stringstream ss; + ss << element; + return ss.str(); + } }; + static const auto catch2_serialization{ []( const auto& element ) { + return Catch::StringMaker::convert( element ); + } }; + + int counter{ 0 }; + + SECTION( "Tuple Implicit" ) { + const auto accessor = GENERATE( tuple_as_range( 42, "foo", 3.14 ) ); + accessor.perform( [&]( const auto& element ) { + REQUIRE( std_serialization( element ) == + catch2_serialization( element ) ); + ++counter; + } ); + } + + SECTION( "Tuple Explicit" ) { + const auto accessor = + GENERATE( tuple_as_range>( + 42, "foo", 3.14 ) ); + accessor.perform( [&]( const auto& element ) { + REQUIRE( std_serialization( element ) == + catch2_serialization( element ) ); + ++counter; + } ); + } + + // also work with std::pair or a tuple-like + + SECTION( "Pair" ) { + const auto accessor_pair = GENERATE( + tuple_as_range>( 42, "foo" ) ); + accessor_pair.perform( [&]( const auto& element ) { + REQUIRE( std_serialization( element ) == + catch2_serialization( element ) ); + ++counter; + } ); + } + + REQUIRE( counter == 1 ); + } + + SECTION( "Single Parameter" ) { + SECTION( "Tuple" ) { + auto test = std::tuple( 1, 2.0, 3.0f ); + const auto accessor = GENERATE_REF( tuple_as_range( test ) ); + SUCCEED(); + std::ignore = accessor; + } + + SECTION( "Pair" ) { + auto test = std::pair( 42, "foo" ); + const auto accessor = GENERATE_REF( tuple_as_range( test ) ); + SUCCEED(); + std::ignore = accessor; + } + + SECTION( "Any" ) { + const auto accessor = GENERATE( tuple_as_range( 1 ) ); + SUCCEED(); + std::ignore = accessor; + } + } } From e60389543e4675f08ebf7634a557f9ddb10b329f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Devaucoup?= Date: Wed, 7 Jan 2026 23:02:42 +0100 Subject: [PATCH 4/5] Allow std::array in TupleLike logic --- .../generators/catch_generators_tuple.hpp | 20 ++++++++++++++++++- .../SelfTest/UsageTests/Generators.tests.cpp | 17 ++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/catch2/generators/catch_generators_tuple.hpp b/src/catch2/generators/catch_generators_tuple.hpp index 6d27673833..53fe8b871d 100644 --- a/src/catch2/generators/catch_generators_tuple.hpp +++ b/src/catch2/generators/catch_generators_tuple.hpp @@ -83,6 +83,22 @@ namespace Catch { TupleType m_tuple; std::size_t m_current_index; + template + struct in_place_tuple { + template + static constexpr TupleType make( Args&&... args ) { + return TupleType{ CATCH_FORWARD( args )... }; + } + }; + + template + struct in_place_tuple> { + template + static constexpr TupleType make( Args&&... args ) { + return TupleType{ { CATCH_FORWARD( args )... } }; + } + }; + public: template constexpr TupleAccessor( TupleLike&& tuple ): @@ -90,7 +106,9 @@ namespace Catch { template constexpr TupleAccessor( Args&&... args ): - m_tuple{ CATCH_FORWARD( args )... }, m_current_index{ 0 } {} + m_tuple{ in_place_tuple::make( + CATCH_FORWARD( args )... ) }, + m_current_index{ 0 } {} constexpr bool next() { ++m_current_index; diff --git a/tests/SelfTest/UsageTests/Generators.tests.cpp b/tests/SelfTest/UsageTests/Generators.tests.cpp index cbe47e1e60..06920e86e9 100644 --- a/tests/SelfTest/UsageTests/Generators.tests.cpp +++ b/tests/SelfTest/UsageTests/Generators.tests.cpp @@ -372,6 +372,16 @@ TEST_CASE( "Tuple", "[generators]" ) { } ); } + SECTION( "Array" ) { + const auto accessor_array = + GENERATE( tuple_as_range>( 1, 2, 3 ) ); + accessor_array.perform( [&]( const auto& element ) { + REQUIRE( std_serialization( element ) == + catch2_serialization( element ) ); + ++counter; + } ); + } + REQUIRE( counter == 1 ); } @@ -390,6 +400,13 @@ TEST_CASE( "Tuple", "[generators]" ) { std::ignore = accessor; } + SECTION( "Array" ) { + auto test = std::array{ { 1, 2, 3 } }; + const auto accessor = GENERATE_REF( tuple_as_range( test ) ); + SUCCEED(); + std::ignore = accessor; + } + SECTION( "Any" ) { const auto accessor = GENERATE( tuple_as_range( 1 ) ); SUCCEED(); From bae18d8057539d14487998eadd12b47abb32aace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Devaucoup?= Date: Thu, 8 Jan 2026 17:38:10 +0100 Subject: [PATCH 5/5] Fix c++14 compilation --- src/catch2/generators/catch_generators_tuple.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/catch2/generators/catch_generators_tuple.hpp b/src/catch2/generators/catch_generators_tuple.hpp index 53fe8b871d..62cbfe93d9 100644 --- a/src/catch2/generators/catch_generators_tuple.hpp +++ b/src/catch2/generators/catch_generators_tuple.hpp @@ -21,10 +21,10 @@ namespace Catch { struct is_tuple_like : std::false_type {}; template - struct is_tuple_like< - T, - std::void_t::value ), - decltype( std::get<0>( std::declval() ) )>> + struct is_tuple_like::value ), + void( std::get<0>( + std::declval() ) ) )> : std::true_type {}; template