diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 40e4455..ec1b661 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -27,6 +27,26 @@ jobs: conan profile detect --name=default make CONAN_PROFILE=.github/workflows/gcc15_c++26 + linux-gcc14-build: + runs-on: ubuntu-latest + container: + image: ubuntu:24.04 + steps: + - uses: actions/checkout@v3 + - name: Update APT + run: sudo apt-get update && sudo apt-get upgrade -y + - name: Install GCC-14 and CMake + run: sudo apt-get install -y gcc-14 g++-14 cmake + - name: Install Conan 2.0 + run: | + sudo apt-get install -y pipx + pipx install conan + echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Build and run unit tests + run: | + conan profile detect --name=default + make CONAN_PROFILE=.github/workflows/gcc14_c++23 + # windows-build: # runs-on: windows-latest # steps: diff --git a/.github/workflows/gcc14_c++23 b/.github/workflows/gcc14_c++23 new file mode 100644 index 0000000..72ef4bf --- /dev/null +++ b/.github/workflows/gcc14_c++23 @@ -0,0 +1,15 @@ +[settings] +os=Linux +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=23 +compiler.version=14 +compiler.libcxx=libstdc++11 + +[buildenv] +CC=gcc-14 +CXX=g++-14 + +[options] +gcc14_compat=True \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e79a9de..6d4a51b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,20 @@ project( LANGUAGES CXX) # ################### Options #################### -set(CMAKE_CXX_STANDARD 26) +option(MELON_ENABLE_GCC14_SUPPORT + "Enable the optional GNU++14/C++23 compatibility layer." ON) +if(MELON_ENABLE_GCC14_SUPPORT) + set(_melon_default_cxx 23) +else() + set(_melon_default_cxx 26) +endif() +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD ${_melon_default_cxx}) +endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +option(MELON_BUILD_TESTS "Build the melon test suite" ON) +option(MELON_FROM_CONAN "Indicates the project is configured from Conan" OFF) # ################### Modules #################### include(GNUInstallDirs) @@ -17,3 +29,8 @@ add_library(melon INTERFACE) target_include_directories( melon INTERFACE $ $) + +# ################### Tests #################### +if(MELON_BUILD_TESTS) + add_subdirectory(test) +endif() diff --git a/README.md b/README.md index ae37820..445997b 100755 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Work in progress. ## How to link -:warning: Since commit [e494eb8d7db1629af280354dc4d76d3c36ed8701](https://github.com/fhamonic/melon/commit/e494eb8d7db1629af280354dc4d76d3c36ed8701) the use of Range-v3 has been replaced by C++26 ranges functionnalities which are currently only implemented in GCC 15. +:warning: Since commit [e494eb8d7db1629af280354dc4d76d3c36ed8701](https://github.com/fhamonic/melon/commit/e494eb8d7db1629af280354dc4d76d3c36ed8701) the library leans on C++26 ranges functionalities which are currently only implemented in GCC 15. | Compiler | Minimum version | | ----------- | --------------- | @@ -21,6 +21,8 @@ Work in progress. | Clang | -- | | MSVC | -- | +:bulb: Need to stay on GCC 14 / C++23? Configure with `-DMELON_ENABLE_GCC14_SUPPORT=ON` (or pass `-o melon:gcc14_compat=True` when using Conan) to make Melon use its internal adapter shims so you keep the same API without any extra dependencies. + ### As a local Conan package (latest commit) ``` diff --git a/conanfile.py b/conanfile.py index ada7d91..a766ca3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -9,6 +9,9 @@ class MelonConan(ConanFile): name = "melon" version = "1.0.0-alpha.1" + options = {"gcc14_compat": [True, False]} + default_options = {"gcc14_compat": False} + license = "BSL-1.0" description = ( "A modern and efficient graph library using C++20 ranges and concepts." @@ -21,12 +24,16 @@ class MelonConan(ConanFile): no_copy_source = True generators = "CMakeToolchain", "CMakeDeps" - def requirements(self): + def requirements(self): self.test_requires("gtest/[>=1.10.0 #include "melon/container/d_ary_heap.hpp" +#include "melon/detail/concat_view.hpp" #include "melon/detail/intrusive_iterator_base.hpp" #include "melon/detail/map_if.hpp" #include "melon/detail/prefetch.hpp" @@ -325,7 +326,7 @@ class bidirectional_dijkstra { requires(_Traits::store_path) { assert(path_found()); - return std::ranges::views::concat( + return detail::views::concat( std::ranges::subrange( forward_path_iterator( this, _forward_pred_arcs_map[_midpoint.value()]), diff --git a/include/melon/detail/concat_view.hpp b/include/melon/detail/concat_view.hpp new file mode 100644 index 0000000..0f26ab4 --- /dev/null +++ b/include/melon/detail/concat_view.hpp @@ -0,0 +1,147 @@ +#ifndef MELON_DETAIL_CONCAT_VIEW_HPP +#define MELON_DETAIL_CONCAT_VIEW_HPP + +#include +#include +#include +#include +#include + +namespace fhamonic { +namespace melon { +namespace detail { +namespace views { + +#if defined(__cpp_lib_ranges_concat) + +inline constexpr auto concat = std::views::concat; + +#else + +template +concept concat_view_compatible = + std::ranges::view && std::ranges::view && + std::ranges::input_range && std::ranges::input_range && + std::common_reference_with, + std::ranges::range_reference_t > && + std::common_reference_with, + std::ranges::range_value_t &> && + std::common_reference_with, + std::ranges::range_value_t &>; + +template + requires concat_view_compatible +class concat_view : public std::ranges::view_interface > { +private: + V1 _first; + V2 _second; + + template + using base_t = std::conditional_t, + concat_view >; + + template + class iterator { + using Parent = base_t; + using FirstBase = std::conditional_t; + using SecondBase = std::conditional_t; + + std::ranges::iterator_t _first_it{}; + std::ranges::sentinel_t _first_end{}; + std::ranges::iterator_t _second_it{}; + std::ranges::sentinel_t _second_end{}; + bool _in_first = true; + + void satisfy() { + if(_in_first && _first_it == _first_end) _in_first = false; + } + + public: + using iterator_concept = std::input_iterator_tag; + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = + std::common_type_t, + std::ranges::range_value_t >; + using reference = std::common_reference_t< + std::ranges::range_reference_t, + std::ranges::range_reference_t >; + + iterator() = default; + + iterator(Parent & parent) + : _first_it(std::ranges::begin(parent._first)) + , _first_end(std::ranges::end(parent._first)) + , _second_it(std::ranges::begin(parent._second)) + , _second_end(std::ranges::end(parent._second)) { + satisfy(); + } + + reference operator*() const { + return _in_first ? *_first_it : *_second_it; + } + + iterator & operator++() { + if(_in_first) { + ++_first_it; + satisfy(); + } else { + ++_second_it; + } + return *this; + } + + void operator++(int) { ++(*this); } + + friend bool operator==(const iterator & it, + std::default_sentinel_t) noexcept { + if(it._in_first) return false; + return it._second_it == it._second_end; + } + }; + +public: + concat_view() + requires std::default_initializable && + std::default_initializable + = default; + + constexpr concat_view(V1 first, V2 second) + : _first(std::move(first)), _second(std::move(second)) {} + + constexpr auto begin() { return iterator(*this); } + + constexpr auto begin() const + requires std::ranges::range && std::ranges::range + { + return iterator(*this); + } + + constexpr auto end() const noexcept { return std::default_sentinel; } + constexpr auto end() noexcept { return std::default_sentinel; } +}; + +template +concat_view(std::views::all_t, std::views::all_t) + -> concat_view, std::views::all_t >; + +struct concat_fn { + template + requires concat_view_compatible, + std::views::all_t > + constexpr auto operator()(R1 && r1, R2 && r2) const { + return concat_view(std::views::all(std::forward(r1)), + std::views::all(std::forward(r2))); + } +}; + +inline constexpr concat_fn concat{}; + +#endif + +} // namespace views +} // namespace detail +} // namespace melon +} // namespace fhamonic + +#endif // MELON_DETAIL_CONCAT_VIEW_HPP diff --git a/include/melon/views/complete_digraph.hpp b/include/melon/views/complete_digraph.hpp index 4c13ede..d9b00f4 100644 --- a/include/melon/views/complete_digraph.hpp +++ b/include/melon/views/complete_digraph.hpp @@ -5,6 +5,7 @@ #include #include "melon/container/static_map.hpp" +#include "melon/detail/concat_view.hpp" #include "melon/mapping.hpp" #include "melon/views/graph_view.hpp" @@ -122,7 +123,7 @@ class complete_digraph : public graph_view_base { [[nodiscard]] constexpr auto in_arcs(const vertex u) const noexcept { assert(u < _num_vertices); const auto increment = static_cast(_num_vertices - 1); - return std::views::concat( + return detail::views::concat( std::ranges::subrange( custom_iota_iterator(static_cast(u - 1), static_cast(u) * increment, diff --git a/include/melon/views/undirect.hpp b/include/melon/views/undirect.hpp index 65800b8..ba7c177 100644 --- a/include/melon/views/undirect.hpp +++ b/include/melon/views/undirect.hpp @@ -4,6 +4,7 @@ #include #include +#include "melon/detail/concat_view.hpp" #include "melon/graph.hpp" #include "melon/views/graph_view.hpp" @@ -61,7 +62,7 @@ class undirect : public undirected_graph_view_base { requires outward_incidence_graph<_Graph> && inward_incidence_graph<_Graph> { - return std::views::concat( + return detail::views::concat( std::views::transform(melon::out_arcs(_graph, u), [&](auto && a) -> std::pair { return {a, melon::arc_target(_graph, a)}; @@ -77,8 +78,8 @@ class undirect : public undirected_graph_view_base { requires outward_adjacency_graph<_Graph> && inward_adjacency_graph<_Graph> { - return std::views::concat(melon::out_neighbors(_graph, u), - melon::in_neighbors(_graph, u)); + return detail::views::concat(melon::out_neighbors(_graph, u), + melon::in_neighbors(_graph, u)); } template diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a46d2ab..cacab10 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,14 +11,31 @@ include(CompilerWarnings) # ################### Packages ################### find_package(GTest) # find_package(mp++ REQUIRED) +if(NOT GTest_FOUND) + if(MELON_FROM_CONAN) + message( + FATAL_ERROR + "GTest must be provided by Conan when MELON_FROM_CONAN is enabled.") + endif() + message(STATUS "GTest not found in registry. Fetching with FetchContent...") + include(FetchContent) + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + FetchContent_MakeAvailable(googletest) +endif() include(GoogleTest) # ################### Library #################### -add_library(melon INTERFACE) -target_include_directories( - melon INTERFACE $ - $) +if(NOT TARGET melon) + add_library(melon INTERFACE) + target_include_directories( + melon INTERFACE $ + $) +endif() # target_link_libraries(melon INTERFACE mp++::mp++) set_project_warnings(melon) @@ -81,6 +98,4 @@ if(NOT "${LIBASAN_PATH}" STREQUAL "libasan.so") target_compile_options(melon_test PRIVATE -fsanitize=address) target_link_options(melon_test PRIVATE -fsanitize=address -lpthread) target_link_libraries(melon_test "${LIBASAN_PATH}") -endif() - - +endif() \ No newline at end of file diff --git a/test/bentley_ottmann.cpp b/test/bentley_ottmann.cpp index ad7d1ab..f6993ce 100755 --- a/test/bentley_ottmann.cpp +++ b/test/bentley_ottmann.cpp @@ -201,10 +201,24 @@ GTEST_TEST(bentley_ottmann, run_integer_example) { for(auto && [i, intersecting_segments] : bentley_ottmann(segments_ids, segments)) { +#if defined(__cpp_lib_format_ranges) && __cpp_lib_format_ranges >= 202207L std::cout << std::format( "({}/{}, {}/{}) : {}\n", int(std::get<0>(i).num()), int(std::get<0>(i).den()), int(std::get<1>(i).num()), int(std::get<1>(i).den()), intersecting_segments); +#else + std::cout << "(" << int(std::get<0>(i).num()) << "/" + << int(std::get<0>(i).den()) << ", " + << int(std::get<1>(i).num()) << "/" + << int(std::get<1>(i).den()) << ") : {"; + bool first = true; + for(auto id : intersecting_segments) { + if(!first) std::cout << ", "; + std::cout << id; + first = false; + } + std::cout << "}\n"; +#endif } } diff --git a/test/subgraph.cpp b/test/subgraph.cpp index 19fdb9e..092f707 100755 --- a/test/subgraph.cpp +++ b/test/subgraph.cpp @@ -3,6 +3,7 @@ #include "melon/algorithm/dijkstra.hpp" #include "melon/container/static_digraph.hpp" +#include "melon/detail/concat_view.hpp" #include "melon/mapping.hpp" #include "melon/utility/static_digraph_builder.hpp" #include "melon/views/graph_view.hpp" @@ -183,7 +184,8 @@ GTEST_TEST(induces_subgraph_views, test) { auto [fgraph, length_map] = builder.build(); auto graph = views::induced_subgraph( fgraph, - std::views::concat(std::views::iota(0u, 2u), std::views::iota(4u, 6u))); + detail::views::concat(std::views::iota(0u, 2u), + std::views::iota(4u, 6u))); auto alg = dijkstra(graph, length_map);