Skip to content

Commit 165d5fb

Browse files
committed
Add TupleGenerator and tuple_as_range
1 parent f80956a commit 165d5fb

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright Catch2 Authors
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// (See accompanying file LICENSE.txt or copy at
4+
// https://www.boost.org/LICENSE_1_0.txt)
5+
6+
// SPDX-License-Identifier: BSL-1.0
7+
#ifndef CATCH_GENERATORS_TUPLE_HPINCLUDED
8+
#define CATCH_GENERATORS_TUPLE_HPINCLUDED
9+
10+
#include <catch2/generators/catch_generators.hpp>
11+
12+
#include <array>
13+
#include <tuple>
14+
#include <type_traits>
15+
16+
namespace Catch {
17+
namespace Generators {
18+
namespace Detail {
19+
20+
template <typename Ret,
21+
typename Tup,
22+
typename Fun,
23+
std::size_t... Idxs>
24+
struct TupleRuntimeAccessTable {
25+
template <std::size_t N>
26+
static constexpr Ret access_tuple( Tup& tuple, Fun& fun ) {
27+
return fun( std::get<N>( tuple ) );
28+
}
29+
30+
using AccessorFunPtr = Ret ( * )( Tup&, Fun& );
31+
static constexpr std::size_t table_size{ sizeof...( Idxs ) };
32+
33+
static constexpr std::array<AccessorFunPtr, table_size>
34+
lookup_table{ { access_tuple<Idxs>... } };
35+
};
36+
37+
template <typename Tup, typename Fun, std::size_t... Idxs>
38+
constexpr auto
39+
call_access_function( Tup& tuple,
40+
std::size_t index,
41+
Fun fun,
42+
std::index_sequence<Idxs...> ) {
43+
using FirstTupleIndexType = decltype( std::get<0>( tuple ) );
44+
using FunReturnType =
45+
std::invoke_result_t<Fun, FirstTupleIndexType>;
46+
47+
constexpr auto& table{
48+
TupleRuntimeAccessTable<FunReturnType, Tup, Fun, Idxs...>::
49+
lookup_table };
50+
return table[index]( tuple, fun );
51+
}
52+
53+
template <typename Tup, typename Fun>
54+
constexpr auto
55+
runtime_get( Tup& tuple, std::size_t index, Fun fun ) {
56+
return call_access_function(
57+
tuple,
58+
index,
59+
CATCH_FORWARD( fun ),
60+
std::make_index_sequence<std::tuple_size_v<Tup>>{} );
61+
}
62+
63+
template <typename Tup>
64+
class TupleAccessor {
65+
Tup m_tuple;
66+
std::size_t m_current_index;
67+
68+
public:
69+
template <typename... Args>
70+
constexpr TupleAccessor( Args&&... args ):
71+
m_tuple{ CATCH_FORWARD( args )... }, m_current_index{ 0 } {}
72+
73+
constexpr TupleAccessor& operator++() {
74+
++m_current_index;
75+
return *this;
76+
}
77+
78+
constexpr operator bool() const {
79+
return m_current_index < std::tuple_size_v<Tup>;
80+
}
81+
82+
template <typename Fun>
83+
constexpr auto perform( Fun fun ) const {
84+
return runtime_get(
85+
m_tuple, m_current_index, CATCH_FORWARD( fun ) );
86+
}
87+
};
88+
89+
} // namespace Detail
90+
91+
template <typename Tup>
92+
class TupleGenerator final
93+
: public IGenerator<Detail::TupleAccessor<Tup>> {
94+
Detail::TupleAccessor<Tup> m_iterator;
95+
96+
public:
97+
template <typename... Args>
98+
TupleGenerator( Args&&... args ):
99+
m_iterator{ CATCH_FORWARD( args )... } {}
100+
101+
const Detail::TupleAccessor<Tup>& get() const override {
102+
return m_iterator;
103+
}
104+
105+
bool next() override { return ++m_iterator; }
106+
};
107+
108+
template <typename Tup, typename... Args>
109+
auto tuple_as_range( Args&&... args ) {
110+
return GeneratorWrapper<Detail::TupleAccessor<Tup>>(
111+
Catch::Detail::make_unique<TupleGenerator<Tup>>(
112+
CATCH_FORWARD( args )... ) );
113+
}
114+
115+
template <typename... Args>
116+
auto tuple_as_range( Args&&... args ) {
117+
return tuple_as_range<std::tuple<Args...>>(
118+
CATCH_FORWARD( args )... );
119+
}
120+
121+
} // namespace Generators
122+
} // namespace Catch
123+
124+
#endif // CATCH_GENERATORS_TUPLE_HPINCLUDED

tests/SelfTest/UsageTests/Generators.tests.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
// SPDX-License-Identifier: BSL-1.0
88

99
#include <catch2/catch_test_macros.hpp>
10+
#include <catch2/catch_tostring.hpp>
1011
#include <catch2/generators/catch_generator_exception.hpp>
1112
#include <catch2/generators/catch_generators_adapters.hpp>
1213
#include <catch2/generators/catch_generators_random.hpp>
1314
#include <catch2/generators/catch_generators_range.hpp>
15+
#include <catch2/generators/catch_generators_tuple.hpp>
1416

1517
#include <cstring>
18+
#include <sstream>
1619

1720

1821
// Generators and sections can be nested freely
@@ -321,3 +324,36 @@ TEST_CASE( "GENERATE can combine literals and generators", "[generators]" ) {
321324
random( -100, 100 ) ) ) );
322325
REQUIRE( i % 2 == 0 );
323326
}
327+
328+
TEST_CASE( "Tuple", "[generators]" ) {
329+
// imagine two different serialization to test
330+
// once a serialization is tested, the second must be identical
331+
static const auto std_serialization{ []( const auto& element ) {
332+
std::stringstream ss;
333+
ss << element;
334+
return ss.str();
335+
} };
336+
static const auto catch2_serialization{ []( const auto& element ) {
337+
return Catch::StringMaker<decltype( element )>::convert( element );
338+
} };
339+
340+
int counter{ 0 };
341+
342+
const auto accessor = GENERATE( tuple_as_range( 42, "foo", 3.14 ) );
343+
accessor.perform( [&]( const auto& element ) {
344+
REQUIRE( std_serialization( element ) ==
345+
catch2_serialization( element ) );
346+
++counter;
347+
} );
348+
349+
// also work with std::pair or a tuple-like
350+
const auto accessor_pair =
351+
GENERATE( tuple_as_range<std::pair<int, std::string>>( 42, "foo" ) );
352+
accessor_pair.perform( [&]( const auto& element ) {
353+
REQUIRE( std_serialization( element ) ==
354+
catch2_serialization( element ) );
355+
++counter;
356+
} );
357+
358+
REQUIRE( counter == 2 );
359+
}

0 commit comments

Comments
 (0)