From 25b17d8cc10079d0ee8bddb025902ae47baddf06 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Tue, 15 Feb 2022 15:59:31 +0100 Subject: [PATCH 1/8] generator from iterable c++ types --- inc/fpgen.hpp | 1 + inc/sources.hpp | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 inc/sources.hpp diff --git a/inc/fpgen.hpp b/inc/fpgen.hpp index c3f2d15..19e99cc 100644 --- a/inc/fpgen.hpp +++ b/inc/fpgen.hpp @@ -2,5 +2,6 @@ #define _FPGEN_MAIN #include "generator.hpp" +#include "sources.hpp" #endif diff --git a/inc/sources.hpp b/inc/sources.hpp new file mode 100644 index 0000000..5c12017 --- /dev/null +++ b/inc/sources.hpp @@ -0,0 +1,17 @@ +#ifndef _FPGEN_SOURCES +#define _FPGEN_SOURCES + +#include "generator.hpp" +#include + +namespace fpgen { +template typename Container> +generator from(const Container &cont) { + for (auto it = std::begin(cont); it != std::end(cont); ++it) { + co_yield *cont; + } +} + +} // namespace fpgen + +#endif From 0727b88e9f1af88fb008d48a27cd5d6da759a7a0 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Tue, 15 Feb 2022 16:12:30 +0100 Subject: [PATCH 2/8] Cleanup --- inc/__helpers.hpp | 12 ++++++++++++ inc/generator.hpp | 17 ++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 inc/__helpers.hpp diff --git a/inc/__helpers.hpp b/inc/__helpers.hpp new file mode 100644 index 0000000..456c7c7 --- /dev/null +++ b/inc/__helpers.hpp @@ -0,0 +1,12 @@ +#ifndef _FPGEN___HELPERS +#define _FPGEN___HELPERS + +#ifdef __clang__ +#include +#define _FPGEN_USE_CONCEPTS 0 +#else +#include +#define _FPGEN_USE_CONCEPTS 1 +#endif + +#endif diff --git a/inc/generator.hpp b/inc/generator.hpp index 32eee9c..2893d4e 100644 --- a/inc/generator.hpp +++ b/inc/generator.hpp @@ -2,29 +2,22 @@ #define _FPGEN_GENERATOR #ifdef __clang__ -#include #include namespace std { using namespace experimental; } #else -#include #include #endif +#include "__helpers.hpp" #include /** * @brief The namespace containing all of fpgen's code. */ namespace fpgen { -#ifdef __clang__ -template -using enabler = - typename std::enable_if::value, bool>::type; - -template = true> class generator { -#else +#if _FPGEN_USE_CONCEPTS /** * \brief The main generator type. * @@ -39,6 +32,12 @@ template = true> class generator { * `std::copyable`, or on (older) CLang versions, `std::is_copy_assignable`. */ template class generator { +#else +template +using enabler = + typename std::enable_if::value, bool>::type; + +template = true> class generator { #endif public: /** From cb67c70d0b1c1bf0f9d6b352415d5b28ad9426fa Mon Sep 17 00:00:00 2001 From: jay-tux Date: Wed, 16 Feb 2022 10:23:26 +0100 Subject: [PATCH 3/8] Bugfix in generator (++iterator issue), added some sources (from iterable, infinite increment) --- inc/generator.hpp | 2 ++ inc/sources.hpp | 27 +++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/inc/generator.hpp b/inc/generator.hpp index 2893d4e..03a1f0e 100644 --- a/inc/generator.hpp +++ b/inc/generator.hpp @@ -201,6 +201,8 @@ template = true> class generator { if (!source) is_finished = true; value = source(); + if (!source) + is_finished = true; return {source, is_finished, value}; } diff --git a/inc/sources.hpp b/inc/sources.hpp index 5c12017..a35b27f 100644 --- a/inc/sources.hpp +++ b/inc/sources.hpp @@ -2,13 +2,32 @@ #define _FPGEN_SOURCES #include "generator.hpp" +#include #include namespace fpgen { -template typename Container> -generator from(const Container &cont) { - for (auto it = std::begin(cont); it != std::end(cont); ++it) { - co_yield *cont; +template typename Container> +generator from(const Container &cont) { + for (auto it = cont.begin(); it != cont.end(); ++it) { + co_yield *it; + } +} + +template typename Container> +generator> +from_tup(const Container &cont) { + for (auto it = cont.begin(); it != cont.end(); ++it) { + co_yield *it; + } +} + +template generator inc(T start) { + T value = start; + while (true) { + co_yield value; + ++value; } } From ad0ea306f808bc85c54981d0e6f887e7b111f553 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Wed, 16 Feb 2022 10:24:18 +0100 Subject: [PATCH 4/8] Using single-main file --- test/Makefile | 19 +++++++---- test/src/test_generator.cpp | 65 +++++++++++++++++++++++++++++++++++++ test/src/test_main.cpp | 3 ++ 3 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 test/src/test_generator.cpp create mode 100644 test/src/test_main.cpp diff --git a/test/Makefile b/test/Makefile index 4f1388c..010828e 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,8 +1,12 @@ SOURCES=$(shell find $(SRCD) -name '*.cpp') DEPS=$(SOURCES:$(SRCD)/%.cpp=$(OBJD)/%.d) -TESTS=generator +TESTS=generator sources +TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o) -all: dirs conan/conanbuildinfo.mak $(BIND)/generator +CONAN_PKG_OVERRIDE=gtest +CONAN_MODIFY=$(CONAN_PKG_OVERRIDE:%=-s %:compiler.version=11.2) + +all: dirs conan/conanbuildinfo.mak $(BIND)/_test -include $(DEPS) -include conan/conanbuildinfo.mak @@ -11,21 +15,22 @@ LDXTRA=$(CONAN_LIB_DIRS:%=-L%) $(CONAN_LIBS:%=-l%) $(CONAN_SYSTEM_LIBS:%=-l%) dirs: @([ ! -d $(OBJD)/generator ] && mkdir -p $(OBJD)/generator) || true + @([ ! -d $(OBJD)/sources ] && mkdir -p $(OBJD)/sources) || true -$(BIND)/generator: $(OBJD)/generator/test_generator.o - $(CC) $< $(LDXTRA) $(LDARGS) -o $@ - $(BIND)/generator +$(BIND)/_test: $(TESTOBJ) $(OBJD)/test_main.o + $(CC) $(LDXTRA) $(LDARGS) $^ -o $@ + $(BIND)/_test $(OBJD)/%.o: $(SRCD)/%.cpp Makefile $(CC) $(CXXARGS) $(CXXXTRA) $< -o $@ conan/conanbuildinfo.mak: conanfile.txt @([ ! -d conan/ ] && mkdir -p conan/) || true - cd conan && CC=gcc CXX=$(CC) conan install .. --build=gtest + cd conan && CC=gcc CXX=$(CC) conan install .. $(CONAN_MODIFY) --build=gtest clean: find ./bin/ -type f | grep -v '.gitkeep' | xargs rm -rf find ./obj/ -type f | grep -v '.gitkeep' | xargs rm -rf find ./conan/ -type f | grep -v '.gitkeep' | xargs rm -rf -.PHONY: all dirs clean +.PHONY: all dirs clean $(TESTS:%=$(BIND)/%) diff --git a/test/src/test_generator.cpp b/test/src/test_generator.cpp new file mode 100644 index 0000000..231f29a --- /dev/null +++ b/test/src/test_generator.cpp @@ -0,0 +1,65 @@ +#include "generator.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +fpgen::generator empty() { co_return 0; } + +fpgen::generator infinite() { + int value = 0; + while (true) { + co_yield value; + value++; + } +} + +fpgen::generator finite_squares(int min, int max) { + for (int val = min; val < max; val++) { + co_yield val *val; + } + co_return max *max; +} + +TEST(generator, accept_empty_gen) { + auto emptygen = empty(); + SUCCEED(); +} + +TEST(generator, iterator_empty_gen) { + auto gen = empty(); + gen(); + for (auto v : gen) { + FAIL(); + } + SUCCEED(); +} + +TEST(generator, can_call_and_continue) { + auto intgen = infinite(); + int value = intgen(); + EXPECT_EQ(0, value); + value = intgen(); + EXPECT_EQ(1, value); +} + +TEST(generator, can_while_over) { + int value; + int expect = 0; + auto intgen2 = finite_squares(0, 12); + while (intgen2) { + value = intgen2(); + EXPECT_THAT(expect, testing::AllOf(testing::Le(12), testing::Ge(0))); + EXPECT_EQ(expect * expect, value); + expect++; + } +} + +TEST(generator, iterator) { + int expect = -4; + auto intgen3 = finite_squares(-4, 8); + for (auto value : intgen3) { + EXPECT_THAT(expect, testing::AllOf(testing::Le(8), testing::Ge(-4))); + EXPECT_EQ(expect * expect, value); + expect++; + } +} diff --git a/test/src/test_main.cpp b/test/src/test_main.cpp new file mode 100644 index 0000000..4e26ef4 --- /dev/null +++ b/test/src/test_main.cpp @@ -0,0 +1,3 @@ +#include "generator.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" From d7629c1eec44428e3710faae9fa8cdd8cfa0cd19 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Wed, 16 Feb 2022 10:24:33 +0100 Subject: [PATCH 5/8] Tests for sources --- test/src/generator/test_generator.cpp | 70 -------------------------- test/src/test_sources.cpp | 71 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 70 deletions(-) delete mode 100644 test/src/generator/test_generator.cpp create mode 100644 test/src/test_sources.cpp diff --git a/test/src/generator/test_generator.cpp b/test/src/generator/test_generator.cpp deleted file mode 100644 index 5396599..0000000 --- a/test/src/generator/test_generator.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "generator.hpp" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include - -fpgen::generator empty() { co_return 0; } - -fpgen::generator infinite() { - int value = 0; - while (true) { - co_yield value; - value++; - } -} - -fpgen::generator finite_squares(int min, int max) { - for (int val = min; val < max; val++) { - co_yield val *val; - } - co_return max *max; -} - -TEST(generator, accept_empty_gen) { - auto emptygen = empty(); - SUCCEED(); -} - -TEST(generator, iterator_empty_gen) { - auto gen = empty(); - gen(); - for (auto v : gen) { - FAIL(); - } - SUCCEED(); -} - -TEST(generator, can_call_and_continue) { - auto intgen = infinite(); - int value = intgen(); - EXPECT_EQ(0, value); - value = intgen(); - EXPECT_EQ(1, value); -} - -TEST(generator, can_while_over) { - int value; - int expect = 0; - auto intgen2 = finite_squares(0, 12); - while (intgen2) { - value = intgen2(); - EXPECT_THAT(expect, testing::AllOf(testing::Le(12), testing::Ge(0))); - EXPECT_EQ(expect * expect, value); - expect++; - } -} - -TEST(generator, iterator) { - int expect = -4; - auto intgen3 = finite_squares(-4, 8); - for (auto value : intgen3) { - EXPECT_THAT(expect, testing::AllOf(testing::Le(8), testing::Ge(-4))); - EXPECT_EQ(expect * expect, value); - expect++; - } -} - -int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/test/src/test_sources.cpp b/test/src/test_sources.cpp new file mode 100644 index 0000000..298ca18 --- /dev/null +++ b/test/src/test_sources.cpp @@ -0,0 +1,71 @@ +#include "generator.hpp" +#include "sources.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include + +TEST(sources, from_vector) { + std::vector values = {0, 5, 1, 4, 2, 3}; + size_t idx = 0; + for (auto v : fpgen::from(values)) { + EXPECT_EQ(values[idx], v); + idx++; + } +} + +TEST(sources, from_set) { + std::set srcs = {"key 1", "key 2", "key 3", "something"}; + std::set todo = {"key 1", "key 2", "key 3", "something"}; + + for (auto v : fpgen::from(srcs)) { + EXPECT_NE(todo.find(v), todo.end()); + todo.erase(v); + } + EXPECT_TRUE(todo.empty()); +} + +TEST(sources, from_map_tup) { + std::map map = {{"key 1", "value 1"}, + {"key 2", "value 2"}, + {"key 3", "value 3"}, + {"something", "else"}}; + std::set todo = {"key 1", "key 2", "key 3", "something"}; + for (auto v : fpgen::from_tup(map)) { + EXPECT_NE(todo.find(std::get<0>(v)), todo.end()); + EXPECT_EQ(map[std::get<0>(v)], std::get<1>(v)); + todo.erase(std::get<0>(v)); + } + EXPECT_TRUE(todo.empty()); +} + +TEST(sources, incrementable) { + auto gen = fpgen::inc(0); + for (int i = 0; i < 25; i++) { + EXPECT_EQ(gen(), i); + } +} + +TEST(sources, incrementable_struct) { + struct inc_struct { + int value; + inline inc_struct operator++() { + value++; + return {value}; + } + inline inc_struct operator++(int) { + inc_struct next = {value}; + value++; + return next; + } + } v1; + v1.value = 0; + + auto gen = fpgen::inc(v1); + for (int i = 0; i < 25; i++) { + EXPECT_EQ(gen().value, i); + } +} From dd8fa31034eed8fcb96ae5dabc23d38bc4b2132b Mon Sep 17 00:00:00 2001 From: jay-tux Date: Wed, 16 Feb 2022 10:40:28 +0100 Subject: [PATCH 6/8] Added documentation for sources --- inc/generator.hpp | 2 +- inc/sources.hpp | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/inc/generator.hpp b/inc/generator.hpp index 03a1f0e..7fdc3e9 100644 --- a/inc/generator.hpp +++ b/inc/generator.hpp @@ -14,7 +14,7 @@ using namespace experimental; #include /** - * @brief The namespace containing all of fpgen's code. + * \brief The namespace containing all of fpgen's code. */ namespace fpgen { #if _FPGEN_USE_CONCEPTS diff --git a/inc/sources.hpp b/inc/sources.hpp index a35b27f..64f995c 100644 --- a/inc/sources.hpp +++ b/inc/sources.hpp @@ -5,15 +5,48 @@ #include #include +/** + * \brief The namespace containing all of fpgen's code. + */ namespace fpgen { +/** + * \brief Creates a generator over a data source. + * + * The data source should have an iterator (using `std::begin` and `std::end`). + * For most builtin containers (`std::vector`, ...) this is already satisfied. + * For `std::map`, see fpgen::from_tup. + * + * \tparam T The type contained in the container. + * \tparam TArgs Any other template parameters passed to the container. + * \tparam Container The container type. + * \param[in] cont The container to iterate over. + * \returns A new generator which will iterate over the container. + * \see fpgen::from_tup + */ template typename Container> generator from(const Container &cont) { - for (auto it = cont.begin(); it != cont.end(); ++it) { + for (auto it = std::begin(cont); it != std::end(cont); ++it) { co_yield *it; } } +/** + * \brief Creates a generator over an associative data source. + * + * The data source should have an iterator (using `std::begin` and `std::end`). + * For most builtin containers (`std::map`, ...) this is already satisfied. + * The container should have two type arguments. For single-type containers, see + * fpgen::from. + * + * \tparam TKey The first type contained in the container (key type). + * \tparam TValue The second type contained in the container (value type). + * \tparam TArgs Any other template parameters passed to the container. + * \tparam Container The container type. + * \param[in] cont The container to iterate over. + * \returns A new generator which will iterate over the container. + * \see fpgen::from + */ template typename Container> generator> @@ -23,6 +56,17 @@ from_tup(const Container &cont) { } } +/** + * \brief Creates an infinitely incrementing generator. + * + * The generator is contstructed by continuously incrementing (a copy of) the + * given value. While mainly meant for integral types, any type supporting + * operator++() (the prefix increment operator) can be used. + * + * \tparam T The type to increment. + * \param[in] start The initial value. + * \returns An infinite generator which increments a value. + */ template generator inc(T start) { T value = start; while (true) { From f359e83af448f32f91d5d72abc6c2c8c751a6e6d Mon Sep 17 00:00:00 2001 From: jay-tux Date: Wed, 16 Feb 2022 11:00:39 +0100 Subject: [PATCH 7/8] Small clarification --- inc/sources.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inc/sources.hpp b/inc/sources.hpp index 64f995c..b8a5db3 100644 --- a/inc/sources.hpp +++ b/inc/sources.hpp @@ -61,7 +61,8 @@ from_tup(const Container &cont) { * * The generator is contstructed by continuously incrementing (a copy of) the * given value. While mainly meant for integral types, any type supporting - * operator++() (the prefix increment operator) can be used. + * operator++() (the prefix increment operator) can be used. The first value + * returned is the start value itself. * * \tparam T The type to increment. * \param[in] start The initial value. From c0bb63a7b1d0a2bbfe037657411538ae98a2c478 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Wed, 16 Feb 2022 11:16:53 +0100 Subject: [PATCH 8/8] Added enumerate source --- inc/sources.hpp | 27 ++++++++++++++++++++++++++- test/src/test_sources.cpp | 10 ++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/inc/sources.hpp b/inc/sources.hpp index b8a5db3..ac7eeb0 100644 --- a/inc/sources.hpp +++ b/inc/sources.hpp @@ -21,7 +21,7 @@ namespace fpgen { * \tparam Container The container type. * \param[in] cont The container to iterate over. * \returns A new generator which will iterate over the container. - * \see fpgen::from_tup + * \see fpgen::from_tup, fpgen::enumerate */ template typename Container> @@ -31,6 +31,31 @@ generator from(const Container &cont) { } } +/** + * \brief Creates a generator over a data source, with indexing. + * + * The data source does not have to allow indexing, but it is recommended for + * repeatable behaviour. If the index is not needed, use fpgen::from. The data + * source should support iterating (using `std::begin` and `std::end`). + * + * \tparam T The type contained in the container. + * \tparam TArgs Any other template parameters passed to the container. + * \tparam Container The container type. + * \param[in] cont The container to iterate over. + * \returns A new generator which will iterate over the container using index + * and value. + * \see fpgen::from_tup, fpgen::enumerate + */ +template typename Container> +generator> enumerate(const Container &cont) { + size_t i = 0; + for (auto it = std::begin(cont); it != std::end(cont); ++it) { + co_yield {i, *it}; + i++; + } +} + /** * \brief Creates a generator over an associative data source. * diff --git a/test/src/test_sources.cpp b/test/src/test_sources.cpp index 298ca18..d27fb1d 100644 --- a/test/src/test_sources.cpp +++ b/test/src/test_sources.cpp @@ -28,6 +28,16 @@ TEST(sources, from_set) { EXPECT_TRUE(todo.empty()); } +TEST(sources, enumerate_vector) { + std::vector values = { 'a', 'c', 'e', 'k', 'j', 't' }; + size_t prev = 0; + for(auto v : fpgen::enumerate(values)) { + EXPECT_EQ(std::get<0>(v), prev); + EXPECT_EQ(values[prev], std::get<1>(v)); + prev++; + } +} + TEST(sources, from_map_tup) { std::map map = {{"key 1", "value 1"}, {"key 2", "value 2"},