From a3d72c89b7cdee920e37d0bfc9de1d2c3c9c3f7f Mon Sep 17 00:00:00 2001 From: Arash Badie-Modiri Date: Sun, 20 Jul 2025 22:51:16 +0300 Subject: [PATCH] CSVReader should satisfy input_range --- .github/workflows/cmake-multi-platform.yml | 2 + include/internal/common.hpp | 4 ++ include/internal/csv_reader.hpp | 2 + tests/CMakeLists.txt | 1 + tests/test_csv_ranges.cpp | 57 ++++++++++++++++++++++ 5 files changed, 66 insertions(+) create mode 100644 tests/test_csv_ranges.cpp diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index e2cce85..56a2a11 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -26,6 +26,7 @@ jobs: os: [windows-latest, ubuntu-latest] build_type: [Release] c_compiler: [gcc, cl] + cxx_standard: [17, 20] include: - os: windows-latest c_compiler: cl @@ -57,6 +58,7 @@ jobs: # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: > cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCSV_CXX_STANDARD=${{ matrix.cxx_standard }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} diff --git a/include/internal/common.hpp b/include/internal/common.hpp index 4b943db..d94a34e 100644 --- a/include/internal/common.hpp +++ b/include/internal/common.hpp @@ -54,6 +54,10 @@ namespace csv { #define STATIC_ASSERT(x) static_assert(x, "Assertion failed") +#if (defined(CMAKE_CXX_STANDARD) && CMAKE_CXX_STANDARD == 20) || __cplusplus >= 202002L +#define CSV_HAS_CXX20 +#endif + #if (defined(CMAKE_CXX_STANDARD) && CMAKE_CXX_STANDARD == 17) || __cplusplus >= 201703L #define CSV_HAS_CXX17 #endif diff --git a/include/internal/csv_reader.hpp b/include/internal/csv_reader.hpp index 300e7da..98f2f45 100644 --- a/include/internal/csv_reader.hpp +++ b/include/internal/csv_reader.hpp @@ -82,9 +82,11 @@ namespace csv { /** Access the CSVRow held by the iterator */ CONSTEXPR_14 reference operator*() { return this->row; } + CONSTEXPR_14 reference operator*() const { return const_cast(this->row); } /** Return a pointer to the CSVRow the iterator has stopped at */ CONSTEXPR_14 pointer operator->() { return &(this->row); } + CONSTEXPR_14 pointer operator->() const { return const_cast(&(this->row)); } iterator& operator++(); /**< Pre-increment iterator */ iterator operator++(int); /**< Post-increment iterator */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e5b57a8..7ddd9a5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,7 @@ target_sources(csv_test test_data_type.cpp test_raw_csv_data.cpp test_round_trip.cpp + test_csv_ranges.cpp ) target_link_libraries(csv_test csv) target_link_libraries(csv_test Catch2::Catch2WithMain) diff --git a/tests/test_csv_ranges.cpp b/tests/test_csv_ranges.cpp new file mode 100644 index 0000000..258e132 --- /dev/null +++ b/tests/test_csv_ranges.cpp @@ -0,0 +1,57 @@ +#include +#include + +#ifdef CSV_HAS_CXX20 +#include + +TEST_CASE("CSVReader C++20 Ranges Compatibility", "[ranges][cxx20]") { + SECTION("CSVReader works with std::ranges::distance") { + std::stringstream ss("A,B,C\n1,2,3\n4,5,6\n7,8,9"); + csv::CSVReader reader(ss); + + auto count = std::ranges::distance(reader); + REQUIRE(count == 3); + } + + SECTION("CSVReader works with std::views") { + std::stringstream ss("A,B,C\n1,2,3\n4,5,6\n7,8,9\n10,11,12"); + csv::CSVReader reader(ss); + + auto filtered = reader | + std::views::filter([](const csv::CSVRow &row) { + return !row.empty() && row[0].get() > 5; + }); + + int filtered_count = 0; + for (const auto &row : filtered) { + filtered_count++; + int val = row[0].get(); + REQUIRE(val > 5); + } + REQUIRE(filtered_count == 2); // rows with 7 and 10 + } + + SECTION("CSVReader iterator satisfies input_range requirements") { + std::stringstream ss("A,B\n1,2\n3,4"); + csv::CSVReader reader(ss); + + auto it = reader.begin(); + auto end = reader.end(); + + static_assert(std::input_iterator); + static_assert(std::ranges::range); + static_assert(std::ranges::input_range); + static_assert(std::sentinel_for); + + REQUIRE(it != end); + auto row = *it; + REQUIRE(row.size() == 2); + + ++it; + REQUIRE(it != end); + + ++it; + REQUIRE(it == end); + } +} +#endif