diff --git a/README.md b/README.md index 4326e14..53dd642 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Once the time tendency is computed, the fluid PDEs are essentially now cast as a * Parallel-netcdf: https://github.com/Parallel-NetCDF/PnetCDF * This is a dependency for two reasons: (1) NetCDF files are easy to visualize and convenient to work with; (2) The users of this code shouldn't have to write their own parallel I/O. -* Ncview: http://meteora.ucsd.edu/~pierce/ncview_home_page.html +* Ncview: https://cirrus.ucsd.edu/ncview/ * This is the easiest way to visualize NetCDF files. * MPI * For OpenACC: An OpenACC-capable compiler (PGI / Nvidia, Cray, GNU) diff --git a/c/build/cmake_kermit.sh b/c/build/cmake_kermit.sh new file mode 100644 index 0000000..5901101 --- /dev/null +++ b/c/build/cmake_kermit.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +PNETCDF_ROOT=/raid/mhoemmen/pkg/pnetcdf-1.14.0 +SRC_ROOT=/raid/mhoemmen/src/miniWeather/c +OPT_FLAGS="-g -O2" + +cmake \ + -DCMAKE_CXX_COMPILER=mpic++ \ + -DCMAKE_C_COMPILER=mpicc \ + -DCMAKE_Fortran_COMPILER=mpif90 \ + -DCXXFLAGS="${OPT_FLAGS} -I${PNETCDF_ROOT}/include" \ + -DLDFLAGS="-L${PNETCDF_ROOT}/lib -lpnetcdf" \ + -DNX=100 \ + -DNZ=50 \ + -DSIM_TIME=20 \ + -DOUT_FREQ=10 \ + ${SRC_ROOT} diff --git a/c/build/cmake_linux_gnu.sh b/c/build/cmake_linux_gnu.sh new file mode 100644 index 0000000..861e651 --- /dev/null +++ b/c/build/cmake_linux_gnu.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +SRC_ROOT=../../../src/miniWeather/c +OPT_FLAGS="-g -O2" + +#PNETCDF_LIB=/usr/lib/x86_64-linux-gnu +#PNETCDF_LDFLAGS="-L${PNETCDF_LIB} -lpnetcdf" +PNETCDF_LDFLAGS="-lpnetcdf" +#PNETCDF_CXXFLAGS="-I$/usr/include" +PNETCDF_CXXFLAGS="" + +DATA_SPEC="DATA_SPEC_COLLISION" + +cmake \ + -DCMAKE_CXX_COMPILER=mpic++ \ + -DCMAKE_C_COMPILER=mpicc \ + -DCMAKE_Fortran_COMPILER=mpif90 \ + -DCXXFLAGS="${OPT_FLAGS} ${PNETCDF_CXXFLAGS}" \ + -DLDFLAGS="${PNETCDF_LDFLAGS}" \ + -DNX=200 \ + -DNZ=100 \ + -DSIM_TIME=1000 \ + -DOUT_FREQ=10 \ + -DDATA_SPEC="${DATA_SPEC}" \ + ${SRC_ROOT} diff --git a/c/miniWeather_serial.cpp b/c/miniWeather_serial.cpp index 0d04d44..710cb4f 100644 --- a/c/miniWeather_serial.cpp +++ b/c/miniWeather_serial.cpp @@ -16,6 +16,8 @@ #include "pnetcdf.h" #include +#define MINIWEATHER_ONLY_OUTPUT_THETA 1 + constexpr double pi = 3.14159265358979323846264338327; //Pi constexpr double grav = 9.8; //Gravitational acceleration (m / s^2) constexpr double cp = 1004.; //Specific heat of dry air at constant pressure @@ -56,8 +58,9 @@ constexpr double qweights[] = { 0.277777777777777777777777777779E0 , 0.444444444 /////////////////////////////////////////////////////////////////////////////////////// //The x-direction length is twice as long as the z-direction length //So, you'll want to have nx_glob be twice as large as nz_glob -int constexpr nx_glob = _NX; //Number of total cells in the x-direction + int constexpr nz_glob = _NZ; //Number of total cells in the z-direction +int constexpr nx_glob = 2 * nz_glob; //Number of total cells in the x-direction double constexpr sim_time = _SIM_TIME; //How many seconds to run the simulation double constexpr output_freq = _OUT_FREQ; //How frequently to output data to file (in seconds) int constexpr data_spec_int = _DATA_SPEC; //How to initialize the data @@ -132,6 +135,10 @@ int main(int argc, char **argv) { //Initial reductions for mass, kinetic energy, and total energy reductions(mass0,te0); + { + fprintf(stderr, "mass0: %le\n" , mass0); + fprintf(stderr, "te0: %le\n" , te0 ); + } //Output the initial state output(state,etime); @@ -147,7 +154,7 @@ int main(int argc, char **argv) { perform_timestep(state,state_tmp,flux,tend,dt); //Inform the user #ifndef NO_INFORM - if (mainproc) { printf( "Elapsed Time: %lf / %lf\n", etime , sim_time ); } + if (mainproc) { fprintf(stderr, "Elapsed Time: %lf / %lf\n", etime , sim_time ); } #endif //Update the elapsed time and output counter etime = etime + dt; @@ -157,18 +164,27 @@ int main(int argc, char **argv) { output_counter = output_counter - output_freq; output(state,etime); } +#if 0 + { + double mass = 0.0; + double te = 0.0; + reductions(mass, te); + fprintf(stderr, "mass: %le\n" , mass ); + fprintf(stderr, "te: %le\n" , te ); + } +#endif // 0 } auto t2 = std::chrono::steady_clock::now(); if (mainproc) { - std::cout << "CPU Time: " << std::chrono::duration(t2-t1).count() << " sec\n"; + std::cerr << "CPU Time: " << std::chrono::duration(t2-t1).count() << " sec\n"; } //Final reductions for mass, kinetic energy, and total energy reductions(mass,te); if (mainproc) { - printf( "d_mass: %le\n" , (mass - mass0)/mass0 ); - printf( "d_te: %le\n" , (te - te0 )/te0 ); + fprintf(stderr, "d_mass: %le\n" , (mass - mass0)/mass0 ); + fprintf(stderr, "d_te: %le\n" , (te - te0 )/te0 ); } finalize(); @@ -183,6 +199,7 @@ int main(int argc, char **argv) { // q** = q[n] + dt/2 * rhs(q* ) // q[n+1] = q[n] + dt/1 * rhs(q** ) void perform_timestep( double *state , double *state_tmp , double *flux , double *tend , double dt ) { + //fprintf(stderr, "direction_switch: %d\n", direction_switch); if (direction_switch) { //x-direction first semi_discrete_step( state , state , state_tmp , dt / 3 , DIR_X , flux , tend ); @@ -523,9 +540,9 @@ void init( int *argc , char ***argv ) { //If I'm the main process in MPI, display some grid information if (mainproc) { - printf( "nx_glob, nz_glob: %d %d\n", nx_glob, nz_glob); - printf( "dx,dz: %lf %lf\n",dx,dz); - printf( "dt: %lf\n",dt); + fprintf(stderr, "nx_glob, nz_glob: %d %d\n", nx_glob, nz_glob); + fprintf(stderr, "dx,dz: %lf %lf\n",dx,dz); + fprintf(stderr, "dt: %lf\n",dt); } //Want to make sure this info is displayed before further output ierr = MPI_Barrier(MPI_COMM_WORLD); @@ -722,6 +739,7 @@ double sample_ellipse_cosine( double x , double z , double amp , double x0 , dou //The file I/O uses parallel-netcdf, the only external library required for this mini-app. //If it's too cumbersome, you can comment the I/O out, but you'll miss out on some potentially cool graphics void output( double *state , double etime ) { +#if 1 int ncid, t_dimid, x_dimid, z_dimid, dens_varid, uwnd_varid, wwnd_varid, theta_varid, t_varid, dimids[3]; int i, k, ind_r, ind_u, ind_w, ind_t; MPI_Offset st1[1], ct1[1], st3[3], ct3[3]; @@ -729,53 +747,72 @@ void output( double *state , double etime ) { double *dens, *uwnd, *wwnd, *theta; double *etimearr; //Inform the user - if (mainproc) { printf("*** OUTPUT ***\n"); } + if (mainproc) { fprintf(stderr, "*** OUTPUT ***\n"); } //Allocate some (big) temp arrays +#if ! defined(MINIWEATHER_ONLY_OUTPUT_THETA) dens = (double *) malloc(nx*nz*sizeof(double)); uwnd = (double *) malloc(nx*nz*sizeof(double)); wwnd = (double *) malloc(nx*nz*sizeof(double)); +#endif theta = (double *) malloc(nx*nz*sizeof(double)); etimearr = (double *) malloc(1 *sizeof(double)); + // PNetCDF needs an MPI_Info object that is not MPI_INFO_NULL. + // It's possible that earlier PNetCDF versions tolerated MPI_INFO_NULL. + MPI_Info mpi_info; + auto info_err = MPI_Info_create(&mpi_info); + if (info_err != MPI_SUCCESS) { + fprintf(stderr, "Error creating MPI Info object\n"); + MPI_Abort(MPI_COMM_WORLD, -1); + } + //If the elapsed time is zero, create the file. Otherwise, open the file if (etime == 0) { //Create the file - ncwrap( ncmpi_create( MPI_COMM_WORLD , "output.nc" , NC_CLOBBER , MPI_INFO_NULL , &ncid ) , __LINE__ ); + ncwrap( ncmpi_create( MPI_COMM_WORLD , "output.nc" , NC_CLOBBER , mpi_info , &ncid ) , __LINE__ ); //Create the dimensions ncwrap( ncmpi_def_dim( ncid , "t" , (MPI_Offset) NC_UNLIMITED , &t_dimid ) , __LINE__ ); ncwrap( ncmpi_def_dim( ncid , "x" , (MPI_Offset) nx_glob , &x_dimid ) , __LINE__ ); ncwrap( ncmpi_def_dim( ncid , "z" , (MPI_Offset) nz_glob , &z_dimid ) , __LINE__ ); //Create the variables dimids[0] = t_dimid; - ncwrap( ncmpi_def_var( ncid , "t" , NC_DOUBLE , 1 , dimids , &t_varid ) , __LINE__ ); + ncwrap( ncmpi_def_var( ncid , "t_var" , NC_DOUBLE , 1 , dimids , &t_varid ) , __LINE__ ); dimids[0] = t_dimid; dimids[1] = z_dimid; dimids[2] = x_dimid; +#if ! defined(MINIWEATHER_ONLY_OUTPUT_THETA) ncwrap( ncmpi_def_var( ncid , "dens" , NC_DOUBLE , 3 , dimids , &dens_varid ) , __LINE__ ); ncwrap( ncmpi_def_var( ncid , "uwnd" , NC_DOUBLE , 3 , dimids , &uwnd_varid ) , __LINE__ ); ncwrap( ncmpi_def_var( ncid , "wwnd" , NC_DOUBLE , 3 , dimids , &wwnd_varid ) , __LINE__ ); +#endif ncwrap( ncmpi_def_var( ncid , "theta" , NC_DOUBLE , 3 , dimids , &theta_varid ) , __LINE__ ); //End "define" mode ncwrap( ncmpi_enddef( ncid ) , __LINE__ ); } else { //Open the file - ncwrap( ncmpi_open( MPI_COMM_WORLD , "output.nc" , NC_WRITE , MPI_INFO_NULL , &ncid ) , __LINE__ ); + ncwrap( ncmpi_open( MPI_COMM_WORLD , "output.nc" , NC_WRITE , mpi_info , &ncid ) , __LINE__ ); //Get the variable IDs +#if ! defined(MINIWEATHER_ONLY_OUTPUT_THETA) ncwrap( ncmpi_inq_varid( ncid , "dens" , &dens_varid ) , __LINE__ ); ncwrap( ncmpi_inq_varid( ncid , "uwnd" , &uwnd_varid ) , __LINE__ ); ncwrap( ncmpi_inq_varid( ncid , "wwnd" , &wwnd_varid ) , __LINE__ ); +#endif ncwrap( ncmpi_inq_varid( ncid , "theta" , &theta_varid ) , __LINE__ ); - ncwrap( ncmpi_inq_varid( ncid , "t" , &t_varid ) , __LINE__ ); + ncwrap( ncmpi_inq_varid( ncid , "t_var" , &t_varid ) , __LINE__ ); } //Store perturbed values in the temp arrays for output for (k=0; k,$,$,$>: + -Wall> + $<$: + /W4> +) + +message(STATUS "Enabled miniWeather_serial") +add_executable(miniWeather_serial miniWeather_mdspan.cpp miniWeather_common.cpp) +target_include_directories(miniWeather_serial PRIVATE "${PROJECT_SOURCE_DIR}") +target_compile_definitions(miniWeather_serial PRIVATE MINIWEATHER_SERIAL) + +# If building with Kokkos, we've already added the mdspan headers +# to the include path (see above). +if (NOT kokkos_POPULATED) + target_link_libraries(miniWeather_serial PRIVATE std::mdspan) +endif() +target_compile_options(miniWeather_serial PRIVATE + $<$,$,$,$>: + -Wall> + $<$: + /W4> +) + +if (kokkos_POPULATED) + message(STATUS "Enabled miniWeather_kokkos_serial") + add_executable(miniWeather_kokkos_serial miniWeather_mdspan.cpp miniWeather_common.cpp) + target_include_directories(miniWeather_kokkos_serial PRIVATE "${PROJECT_SOURCE_DIR}") + + target_link_libraries(miniWeather_kokkos_serial PRIVATE Kokkos::kokkos) + target_compile_definitions(miniWeather_kokkos_serial PRIVATE MINIWEATHER_KOKKOS) + target_compile_definitions(miniWeather_kokkos_serial PRIVATE MINIWEATHER_KOKKOS_SERIAL) + + # We got mdspan from Kokkos. + target_compile_options(miniWeather_kokkos_serial PRIVATE + $<$,$,$,$>: + -Wall> + $<$: + /W4> + ) +endif() + +if (kokkos_POPULATED AND OpenACC_FOUND) + set(Kokkos_ENABLE_OPENACC ON) + + message(STATUS "Enabled miniWeather_kokkos_openacc") + add_executable(miniWeather_kokkos_openacc miniWeather_mdspan.cpp miniWeather_common.cpp) + target_include_directories(miniWeather_kokkos_openacc PRIVATE "${PROJECT_SOURCE_DIR}") + + target_link_libraries(miniWeather_kokkos_openacc PRIVATE Kokkos::kokkos) + target_compile_definitions(miniWeather_kokkos_openacc PRIVATE MINIWEATHER_KOKKOS) + target_compile_definitions(miniWeather_kokkos_openacc PRIVATE MINIWEATHER_KOKKOS_OPENACC) + + # We got mdspan from Kokkos. + target_link_libraries(miniWeather_kokkos_openacc PRIVATE OpenACC::OpenACC_CXX) + target_compile_options(miniWeather_kokkos_openacc PRIVATE + $<$,$,$,$>: + -Wall> + $<$: + /W4> + ) +endif() + +# We don't have a "pure OpenACC" version yet. +if (FALSE) +if (OpenACC_FOUND) + message(STATUS "Enabled miniWeather_openacc") + add_executable(miniWeather_openacc miniWeather_mdspan.cpp miniWeather_common.cpp) + target_include_directories(miniWeather_openacc PRIVATE "${PROJECT_SOURCE_DIR}") + target_compile_definitions(miniWeather_openacc PRIVATE MINIWEATHER_OPENACC) + + # If building with Kokkos, we've already added the mdspan headers + # to the include path (see above). + if (NOT kokkos_POPULATED) + target_link_libraries(miniWeather_openacc PRIVATE std::mdspan) + endif() + target_link_libraries(miniWeather_openacc PRIVATE OpenACC::OpenACC_CXX) + target_compile_options(miniWeather_openacc PRIVATE + $<$,$,$,$>: + -Wall> + $<$: + /W4> + ) +endif() +endif() + +set(MINIWEATHER_HAVE_STDPAR (${CMAKE_CXX_STANDARD} GREATER_EQUAL 23)) +# Option to enable stdpar +set(MINIWEATHER_ENABLE_STDPAR ${MINIWEATHER_HAVE_STDPAR} CACHE BOOL "Enable stdpar using the -stdpar flag") + +if (MINIWEATHER_ENABLE_STDPAR) + message(STATUS "Enabled miniWeather_stdpar_cpu") + add_executable(miniWeather_stdpar_cpu miniWeather_mdspan.cpp miniWeather_common.cpp) + target_include_directories(miniWeather_stdpar_cpu PRIVATE "${PROJECT_SOURCE_DIR}") + target_compile_definitions(miniWeather_stdpar_cpu PRIVATE MINIWEATHER_STDPAR) + target_compile_definitions(miniWeather_stdpar_cpu PRIVATE MINIWEATHER_STDPAR_CPU) + + # If building with Kokkos, we've already added the mdspan headers + # to the include path (see above). + if (NOT kokkos_POPULATED) + target_link_libraries(miniWeather_stdpar_cpu PRIVATE std::mdspan) + endif() + target_compile_options(miniWeather_stdpar_cpu PRIVATE + $<$,$,$,$>: + -Wall> + $<$: + /W4> + ) + target_compile_options(miniWeather_stdpar_cpu PRIVATE "-stdpar=multicore") + target_link_options(miniWeather_stdpar_cpu PRIVATE "-stdpar=multicore") +endif() + +if (MINIWEATHER_ENABLE_STDPAR) + message(STATUS "Enabled miniWeather_stdpar_gpu") + add_executable(miniWeather_stdpar_gpu miniWeather_mdspan.cpp miniWeather_common.cpp) + target_include_directories(miniWeather_stdpar_gpu PRIVATE "${PROJECT_SOURCE_DIR}") + target_compile_definitions(miniWeather_stdpar_gpu PRIVATE MINIWEATHER_STDPAR) + target_compile_definitions(miniWeather_stdpar_gpu PRIVATE MINIWEATHER_STDPAR_GPU) + + # If building with Kokkos, we've already added the mdspan headers + # to the include path (see above). + if (NOT kokkos_POPULATED) + target_link_libraries(miniWeather_stdpar_gpu PRIVATE std::mdspan) + endif() + target_compile_options(miniWeather_stdpar_gpu PRIVATE + $<$,$,$,$>: + -Wall> + $<$: + /W4> + ) + target_compile_options(miniWeather_stdpar_gpu PRIVATE "-stdpar=gpu") + target_link_options(miniWeather_stdpar_gpu PRIVATE "-stdpar=gpu") +endif() + +if (MINIWEATHER_ENABLE_STDPAR AND OpenACC_FOUND) + message(STATUS "Enabled miniWeather_stdpar_openacc") + add_executable(miniWeather_stdpar_openacc miniWeather_mdspan.cpp miniWeather_common.cpp) + target_include_directories(miniWeather_stdpar_openacc PRIVATE "${PROJECT_SOURCE_DIR}") + target_compile_definitions(miniWeather_stdpar_openacc PRIVATE MINIWEATHER_STDPAR) + target_compile_definitions(miniWeather_stdpar_openacc PRIVATE MINIWEATHER_STDPAR_OPENACC) + + # If building with Kokkos, we've already added the mdspan headers + # to the include path (see above). + if (NOT kokkos_POPULATED) + target_link_libraries(miniWeather_stdpar_openacc PRIVATE std::mdspan) + endif() + target_link_libraries(miniWeather_stdpar_openacc PRIVATE OpenACC::OpenACC_CXX) + target_compile_options(miniWeather_stdpar_openacc PRIVATE + $<$,$,$,$>: + -Wall> + $<$: + /W4> + ) + target_compile_options(miniWeather_stdpar_openacc PRIVATE "-stdpar=gpu:acc") + target_link_options(miniWeather_stdpar_openacc PRIVATE "-stdpar=gpu:acc") +endif() + +# Option to enable CUB +set(MINIWEATHER_ENABLE_CUB ${MINIWEATHER_CUB_FOUND} CACHE BOOL "Enable CUB") + +if (MINIWEATHER_ENABLE_CUB) + message(STATUS "Enabled miniWeather_cub") + add_executable(miniWeather_cub miniWeather_mdspan.cpp miniWeather_common.cpp) + target_include_directories(miniWeather_cub PRIVATE "${PROJECT_SOURCE_DIR}") + target_compile_definitions(miniWeather_cub PRIVATE MINIWEATHER_CUB) + + # If building with Kokkos, we've already added the mdspan headers + # to the include path (see above). + if (NOT kokkos_POPULATED) + target_link_libraries(miniWeather_cub PRIVATE std::mdspan) + endif() + target_compile_options(miniWeather_cub PRIVATE + $<$,$,$,$>: + -Wall> + $<$: + /W4> + ) + # Building CUB with nvc++ requires the "-cuda" flag + target_compile_options(miniWeather_cub PRIVATE + $<$>: + -cuda> + ) + target_link_options(miniWeather_cub PRIVATE + $<$>: + -cuda> + ) +endif() diff --git a/cpp-mdspan/build/cmake-kermit.sh b/cpp-mdspan/build/cmake-kermit.sh new file mode 100755 index 0000000..66c55ff --- /dev/null +++ b/cpp-mdspan/build/cmake-kermit.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# This script only works with nvc++. +# It assumes that mpic++ finds nvc++, mpicc finds nvc (!= nvcc), etc. + +PNETCDF_ROOT=/raid/mhoemmen/pkg/pnetcdf-1.14.0 +PNETCDF_LDFLAGS="-L${PNETCDF_ROOT}/lib -lpnetcdf" +PNETCDF_CXXFLAGS="-I${PNETCDF_ROOT}/include" +SRC_ROOT=/raid/mhoemmen/src/miniWeather/cpp-mdspan + +# "-stdpar": "Could not find librt library, needed by CUDA::cudart_static" +# Adding "-rt" to LDFLAGS didn't seem to help. + +# Current version of stdpar code requires C++23, +# because it uses std::ranges::views::cartesian_product. +# nvc++ might not support that yet. +MINIWEATHER_ENABLE_STDPAR=ON + +# We don't have ForEachInExtents yet with 25.1. +MINIWEATHER_ENABLE_CUB=OFF + +KOKKOS_ROOT="/raid/mhoemmen/src/kokkos/kokkos" +# -DFETCHCONTENT_SOURCE_DIR_Kokkos="${KOKKOS_ROOT}" +# -DKokkos_ROOT="${KOKKOS_ROOT}" + +LDFLAGS="${PNETCDF_LDFLAGS}" CXXFLAGS="${PNETCDF_CXXFLAGS}" cmake \ + -DCMAKE_CXX_COMPILER=mpic++ \ + -DCMAKE_C_COMPILER=mpicc \ + -DCMAKE_Fortran_COMPILER=mpif90 \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ + -DFETCHCONTENT_SOURCE_DIR_KOKKOS="${KOKKOS_ROOT}" \ + -DMINIWEATHER_ENABLE_STDPAR=${MINIWEATHER_ENABLE_STDPAR} \ + -DMINIWEATHER_ENABLE_CUB=${MINIWEATHER_ENABLE_CUB} \ + ${SRC_ROOT} + +# -DCMAKE_CXX_FLAGS="-stdpar" diff --git a/cpp-mdspan/cartesian_product.hpp b/cpp-mdspan/cartesian_product.hpp new file mode 100644 index 0000000..60ed85f --- /dev/null +++ b/cpp-mdspan/cartesian_product.hpp @@ -0,0 +1,2027 @@ +#pragma once +//#define DISABLE_CART_PROD_IOTA_SPEC +/* +Adapted from TartanLlama/ranges: https://github.com/TartanLlama/ranges +Original version License CC0 1.0 Universal (see below) + +Modified by Gonzalo Brito Gadeschi, NVIDIA corporation +Modifications under MIT license. + +--- + +SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: MIT + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +--- + +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tl { + namespace detail { + template + concept single_pass_iterator = std::input_or_output_iterator && !std::forward_iterator; + + template + constexpr auto common_iterator_category() { + if constexpr ((std::ranges::random_access_range && ...)) + return std::random_access_iterator_tag{}; + else if constexpr ((std::ranges::bidirectional_range && ...)) + return std::bidirectional_iterator_tag{}; + else if constexpr ((std::ranges::forward_range && ...)) + return std::forward_iterator_tag{}; + else if constexpr ((std::ranges::input_range && ...)) + return std::input_iterator_tag{}; + else + return std::output_iterator_tag{}; + } + } + + template + using common_iterator_category = decltype(detail::common_iterator_category()); + + template + concept simple_view = std::ranges::view && std::ranges::range && + std::same_as, std::ranges::iterator_t> && + std::same_as, + std::ranges::sentinel_t>; + + struct as_sentinel_t {}; + constexpr inline as_sentinel_t as_sentinel; + + template + using maybe_const = std::conditional_t; + + template + class basic_mixin : protected T { + public: + constexpr basic_mixin() + noexcept(std::is_nothrow_default_constructible::value) + requires std::default_initializable : + T() {} + constexpr basic_mixin(const T& t) + noexcept(std::is_nothrow_copy_constructible::value) + requires std::copy_constructible : + T(t) {} + constexpr basic_mixin(T&& t) + noexcept(std::is_nothrow_move_constructible::value) + requires std::move_constructible : + T(std::move(t)) {} + + + constexpr T& get() & noexcept { return *static_cast(this); } + constexpr const T& get() const& noexcept { return *static_cast(this); } + constexpr T&& get() && noexcept { return std::move(*static_cast(this)); } + constexpr const T&& get() const&& noexcept { return std::move(*static_cast(this)); } + }; + + namespace cursor { + namespace detail { + template + struct tags { + static constexpr auto single_pass() requires requires { { C::single_pass } -> std::convertible_to; } { + return C::single_pass; + } + static constexpr auto single_pass() { return false; } + + static constexpr auto contiguous() requires requires { { C::contiguous } -> std::convertible_to; } { + return C::contiguous; + } + static constexpr auto contiguous() { return false; } + }; + } + template + constexpr bool single_pass = detail::tags::single_pass(); + + template + constexpr bool tagged_contiguous = detail::tags::contiguous(); + + namespace detail { + template + struct deduced_mixin_t { + template static auto deduce(int)-> typename T::mixin; + template static auto deduce(...)->tl::basic_mixin; + using type = decltype(deduce(0)); + }; + } + + template + using mixin_t = typename detail::deduced_mixin_t::type; + + template + requires + requires(const C& c) { c.read(); } + using reference_t = decltype(std::declval().read()); + + namespace detail { + template + struct deduced_value_t { + template static auto deduce(int)-> typename T::value_type; + template static auto deduce(...)->std::decay_t>; + + using type = decltype(deduce(0)); + }; + } + + template + requires std::same_as::type, std::decay_t::type>> + using value_type_t = typename detail::deduced_value_t::type; + + namespace detail { + template + struct deduced_difference_t { + template static auto deduce(int)-> typename T::difference_type; + template + static auto deduce(long)->decltype(std::declval().distance_to(std::declval())); + template + static auto deduce(...)->std::ptrdiff_t; + + using type = decltype(deduce(0)); + }; + } + + template + using difference_type_t = typename detail::deduced_difference_t::type; + + template + concept cursor = std::semiregular> + && std::semiregular>> + && requires {typename difference_type_t; }; + + template + concept readable = cursor && requires(const C & c) { + c.read(); + typename reference_t; + typename value_type_t; + }; + + template + concept arrow = readable + && requires(const C & c) { c.arrow(); }; + + template + concept writable = cursor + && requires(C & c, T && t) { c.write(std::forward(t)); }; + + template + concept sentinel_for = cursor && std::semiregular + && requires(const C & c, const S & s) { {c.equal(s)} -> std::same_as; }; + + template + concept sized_sentinel_for = sentinel_for && + requires(const C & c, const S & s) { + {c.distance_to(s)} -> std::same_as>; + }; + + template + concept next = cursor && requires(C & c) { c.next(); }; + + template + concept prev = cursor && requires(C & c) { c.prev(); }; + + template + concept advance = cursor + && requires(C & c, difference_type_t n) { c.advance(n); }; + + template + concept indirect_move = readable + && requires(const C & c) { c.indirect_move(); }; + + template + concept indirect_swap = readable && readable + && requires(const C & c, const O & o) { + c.indirect_swap(o); + o.indirect_swap(c); + }; + + template + concept input = readable && next; + template + concept forward = input && sentinel_for && !single_pass; + template + concept bidirectional = forward && prev; + template + concept random_access = bidirectional && advance && sized_sentinel_for; + template + concept contiguous = random_access && tagged_contiguous && std::is_reference_v>; + + template + constexpr auto cpp20_iterator_category() { + if constexpr (contiguous) + return std::contiguous_iterator_tag{}; + else if constexpr (random_access) + return std::random_access_iterator_tag{}; + else if constexpr (bidirectional) + return std::bidirectional_iterator_tag{}; + else if constexpr (forward) + return std::forward_iterator_tag{}; + else + return std::input_iterator_tag{}; + } + template + using cpp20_iterator_category_t = decltype(cpp20_iterator_category()); + + //There were a few changes in requirements on iterators between C++17 and C++20 + //See https://wg21.link/p2259 for discussion + //- C++17 didn't have contiguous iterators + //- C++17 input iterators required *it++ to be valid + //- C++17 forward iterators required the reference type to be exactly value_type&/value_type const& (i.e. not a proxy) + struct not_a_cpp17_iterator {}; + + template + concept reference_is_value_type_ref = + (std::same_as, value_type_t&> || std::same_as, value_type_t const&>); + + template + concept can_create_postincrement_proxy = + (std::move_constructible> && std::constructible_from, reference_t>); + + template + constexpr auto cpp17_iterator_category() { + if constexpr (random_access +#if !defined(__NVCOMPILER) + // YOLO: with nvc++ proxy iterators can be random access . . . + // BUG: Need to update Thrust to C++20 iterator categories + && reference_is_value_type_ref +#endif + ) + return std::random_access_iterator_tag{}; + else if constexpr (bidirectional && reference_is_value_type_ref) + return std::bidirectional_iterator_tag{}; + else if constexpr (forward && reference_is_value_type_ref) + return std::forward_iterator_tag{}; + else if constexpr (can_create_postincrement_proxy) + return std::input_iterator_tag{}; + else + return not_a_cpp17_iterator{}; + } + template + using cpp17_iterator_category_t = decltype(cpp17_iterator_category()); + + //iterator_concept and iterator_category are tricky; this abstracts them out. + //Since the rules for iterator categories changed between C++17 and C++20 + //a C++20 iterator may have a weaker category in C++17, + //or it might not be a valid C++17 iterator at all. + //iterator_concept will be the C++20 iterator category. + //iterator_category will be the C++17 iterator category, or it will not exist + //in the case that the iterator is not a valid C++17 iterator. + template > + struct associated_types_category_base { + using iterator_category = category; + }; + template + struct associated_types_category_base {}; + + template + struct associated_types : associated_types_category_base { + using iterator_concept = cpp20_iterator_category_t; + using value_type = cursor::value_type_t; + using difference_type = cursor::difference_type_t; + using reference = cursor::reference_t; + }; + + namespace detail { + // We assume a cursor is writeable if it's either not readable + // or it is writeable with the same type it reads to + template + struct is_writable_cursor { + template + requires requires (C c) { + c.write(c.read()); + } + static auto deduce()->std::true_type; + + template + static auto deduce()->std::false_type; + + template + static auto deduce()->std::true_type; + + static constexpr bool value = decltype(deduce())::value; + }; + } + } + + namespace detail { + template + struct post_increment_proxy { + private: + T cache_; + + public: + template + constexpr post_increment_proxy(U&& t) + : cache_(std::forward(t)) + {} + constexpr T const& operator*() const noexcept + { + return cache_; + } + }; + } + + + template + class basic_iterator : + public cursor::mixin_t + { + private: + using mixin = cursor::mixin_t; + + constexpr auto& cursor() noexcept { return this->mixin::get(); } + constexpr auto const& cursor() const noexcept { return this->mixin::get(); } + + template + friend class basic_iterator; + + //TODO these need to change to support output iterators + using reference_t = decltype(std::declval().read()); + using const_reference_t = reference_t; + + public: + using mixin::get; + + using value_type = cursor::value_type_t; + using difference_type = cursor::difference_type_t; + using reference = cursor::reference_t; + + basic_iterator() = default; + + using mixin::mixin; + + constexpr explicit basic_iterator(C&& c) + noexcept(std::is_nothrow_constructible_v) : + mixin(std::move(c)) {} + + + constexpr explicit basic_iterator(C const& c) + noexcept(std::is_nothrow_constructible_v) : + mixin(c) {} + + template O> + constexpr basic_iterator(basic_iterator&& that) + noexcept(std::is_nothrow_constructible::value) : + mixin(that.cursor()) {} + + template O> + constexpr basic_iterator(const basic_iterator& that) + noexcept(std::is_nothrow_constructible::value) : + mixin(std::move(that.cursor())) {} + + template O> + constexpr basic_iterator& operator=(basic_iterator&& that) & + noexcept(std::is_nothrow_assignable::value) { + cursor() = std::move(that.cursor()); + return *this; + } + + template O> + constexpr basic_iterator& operator=(const basic_iterator& that) & + noexcept(std::is_nothrow_assignable::value) { + cursor() = that.cursor(); + return *this; + } + + template + requires + (!std::same_as, basic_iterator> && + !cursor::next&& + cursor::writable) + constexpr basic_iterator& operator=(T&& t) & + noexcept(noexcept(std::declval().write(static_cast(t)))) { + cursor() = std::forward(t); + return *this; + } + + friend constexpr decltype(auto) iter_move(const basic_iterator& i) +#if !defined(__NVCOMPILER) + noexcept(noexcept(i.cursor().indirect_move())) +#endif + requires cursor::indirect_move { + return i.cursor().indirect_move(); + } + + template + requires cursor::indirect_swap + friend constexpr void iter_swap( + const basic_iterator& x, const basic_iterator& y) +#if !defined(__NVCOMPILER) + noexcept(noexcept((void)x.indirect_swap(y))) +#endif + { + x.indirect_swap(y); + } + + //Input iterator + constexpr decltype(auto) operator*() const + noexcept(noexcept(std::declval().read())) + requires (cursor::readable && !cursor::detail::is_writable_cursor::value) { + return cursor().read(); + } + + //Output iterator + constexpr decltype(auto) operator*() + noexcept(noexcept(reference_t{ cursor() })) + requires (cursor::next&& cursor::detail::is_writable_cursor::value) { + return reference_t{ cursor() }; + } + + //Output iterator + constexpr decltype(auto) operator*() const + noexcept(noexcept( + const_reference_t{ cursor() })) + requires (cursor::next&& cursor::detail::is_writable_cursor::value) { + return const_reference_t{ cursor() }; + } + + constexpr basic_iterator& operator*() noexcept + requires (!cursor::next) { + return *this; + } + + // operator->: "Manual" deduction override, + constexpr decltype(auto) operator->() const + noexcept(noexcept(cursor().arrow())) + requires cursor::arrow { + return cursor().arrow(); + } + // operator->: Otherwise, if reference_t is an lvalue reference, + constexpr decltype(auto) operator->() const + noexcept(noexcept(*std::declval())) + requires (cursor::readable && !cursor::arrow) + && std::is_lvalue_reference::value{ + return std::addressof(**this); + } + + // modifiers + constexpr basic_iterator& operator++() & noexcept { + return *this; + } + constexpr basic_iterator& operator++() & + noexcept(noexcept(std::declval().cursor().next())) + requires cursor::next { + cursor().next(); + return *this; + } + + //C++17 required that *it++ was valid. + //For input iterators, we can't copy *this, so we need to create a proxy reference. + constexpr auto operator++(int) & + noexcept(noexcept(++std::declval()) && + std::is_nothrow_move_constructible_v&& + std::is_nothrow_constructible_v) + requires (cursor::single_pass&& + std::move_constructible&& + std::constructible_from) { + detail::post_increment_proxy p(**this); + ++* this; + return p; + } + + //If we can't create a proxy reference, it++ is going to return void + constexpr void operator++(int) & + noexcept(noexcept(++std::declval())) + requires (cursor::single_pass && !(std::move_constructible&& + std::constructible_from)) { + (void)(++(*this)); + } + + //If C is a forward cursor then copying it is fine + constexpr basic_iterator operator++(int) & + noexcept(std::is_nothrow_copy_constructible_v&& + std::is_nothrow_move_constructible_v && + noexcept(++std::declval())) + requires (!cursor::single_pass) { + auto temp = *this; + ++* this; + return temp; + } + + constexpr basic_iterator& operator--() & + noexcept(noexcept(cursor().prev())) + requires cursor::bidirectional { + cursor().prev(); + return *this; + } + + //Postfix decrement doesn't have the same issue as postfix increment + //because bidirectional requires the cursor to be a forward cursor anyway + //so copying it is fine. + constexpr basic_iterator operator--(int) & + noexcept(std::is_nothrow_copy_constructible::value&& + std::is_nothrow_move_constructible::value && + noexcept(--std::declval())) + requires cursor::bidirectional { + auto tmp = *this; + --* this; + return tmp; + } + + constexpr basic_iterator& operator+=(difference_type n) & + noexcept(noexcept(cursor().advance(n))) + requires cursor::random_access { + cursor().advance(n); + return *this; + } + + constexpr basic_iterator& operator-=(difference_type n) & + noexcept(noexcept(cursor().advance(-n))) + requires cursor::random_access { + cursor().advance(-n); + return *this; + } + + constexpr decltype(auto) operator[](difference_type n) const + noexcept(noexcept(*(std::declval() + n))) + requires cursor::random_access { + return *(*this + n); + } + + // non-template type-symmetric ops to enable implicit conversions + friend constexpr difference_type operator-( + const basic_iterator& x, const basic_iterator& y) + noexcept(noexcept(y.cursor().distance_to(x.cursor()))) + requires cursor::sized_sentinel_for { + return y.cursor().distance_to(x.cursor()); + } + friend constexpr bool operator==( + const basic_iterator& x, const basic_iterator& y) +#if !defined(__NVCOMPILER) + noexcept(noexcept(x.cursor().equal(y.cursor()))) + requires cursor::sentinel_for +#endif + { + return x.cursor().equal(y.cursor()); + } + friend constexpr bool operator!=( + const basic_iterator& x, const basic_iterator& y) +#if !defined(__NVCOMPILER) + noexcept(noexcept(!(x == y))) + requires cursor::sentinel_for +#endif + { + return !(x == y); + } + friend constexpr bool operator<( + const basic_iterator& x, const basic_iterator& y) +#if !defined(__NVCOMPILER) + noexcept(noexcept(y - x)) +#endif + requires cursor::sized_sentinel_for { + return 0 < (y - x); + } + friend constexpr bool operator>( + const basic_iterator& x, const basic_iterator& y) +#if !defined(__NVCOMPILER) + noexcept(noexcept(y - x)) +#endif + requires cursor::sized_sentinel_for { + return 0 > (y - x); + } + friend constexpr bool operator<=( + const basic_iterator& x, const basic_iterator& y) +#if !defined(__NVCOMPILER) + noexcept(noexcept(y - x)) +#endif + requires cursor::sized_sentinel_for { + return 0 <= (y - x); + } + friend constexpr bool operator>=( + const basic_iterator& x, const basic_iterator& y) +#if !defined(__NVCOMPILER) + noexcept(noexcept(y - x)) +#endif + requires cursor::sized_sentinel_for { + return 0 >= (y - x); + } + }; + + namespace detail { + template + struct is_basic_iterator { + template + static auto deduce(basic_iterator const&)->std::true_type; + template + static auto deduce(...)->std::false_type; + static constexpr inline bool value = decltype(deduce(std::declval()))::value; + }; + } + + // basic_iterator nonmember functions + template + constexpr basic_iterator operator+( + const basic_iterator& i, cursor::difference_type_t n) + noexcept(std::is_nothrow_copy_constructible>::value&& + std::is_nothrow_move_constructible>::value && + noexcept(std::declval&>() += n)) + requires cursor::random_access { + auto tmp = i; + tmp += n; + return tmp; + } + template + constexpr basic_iterator operator+( + cursor::difference_type_t n, const basic_iterator& i) + noexcept(noexcept(i + n)) + requires cursor::random_access { + return i + n; + } + + template + constexpr basic_iterator operator-( + const basic_iterator& i, cursor::difference_type_t n) + noexcept(noexcept(i + (-n))) + requires cursor::random_access { + return i + (-n); + } + template + requires cursor::sized_sentinel_for + constexpr cursor::difference_type_t operator-( + const basic_iterator& lhs, const basic_iterator& rhs) + noexcept(noexcept( + rhs.get().distance_to(lhs.get()))) { + return rhs.get().distance_to(lhs.get()); + } + template + requires cursor::sized_sentinel_for + constexpr cursor::difference_type_t operator-( + const S& lhs, const basic_iterator& rhs) + noexcept(noexcept(rhs.get().distance_to(lhs))) { + return rhs.get().distance_to(lhs); + } + template + requires cursor::sized_sentinel_for + constexpr cursor::difference_type_t operator-( + const basic_iterator& lhs, const S& rhs) + noexcept(noexcept(-(rhs - lhs))) { + return -(rhs - lhs); + } + + template + requires cursor::sentinel_for + constexpr bool operator==( + const basic_iterator& lhs, const basic_iterator& rhs) + noexcept(noexcept(lhs.get().equal(rhs.get()))) { + return lhs.get().equal(rhs.get()); + } + template + requires cursor::sentinel_for + constexpr bool operator==( + const basic_iterator& lhs, const S& rhs) + noexcept(noexcept(lhs.get().equal(rhs))) { + return lhs.get().equal(rhs); + } + template + requires cursor::sentinel_for + constexpr bool operator==( + const S& lhs, const basic_iterator& rhs) + noexcept(noexcept(rhs == lhs)) { + return rhs == lhs; + } + + template + requires cursor::sentinel_for + constexpr bool operator!=( + const basic_iterator& lhs, const basic_iterator& rhs) + noexcept(noexcept(!(lhs == rhs))) { + return !(lhs == rhs); + } + template + requires cursor::sentinel_for + constexpr bool operator!=( + const basic_iterator& lhs, const S& rhs) + noexcept(noexcept(!lhs.get().equal(rhs))) { + return !lhs.get().equal(rhs); + } + template + requires cursor::sentinel_for + constexpr bool operator!=( + const S& lhs, const basic_iterator& rhs) + noexcept(noexcept(!rhs.get().equal(lhs))) { + return !rhs.get().equal(lhs); + } + + template + requires cursor::sized_sentinel_for + constexpr bool operator<( + const basic_iterator& lhs, const basic_iterator& rhs) + noexcept(noexcept(lhs - rhs < 0)) { + return (lhs - rhs) < 0; + } + + template + requires cursor::sized_sentinel_for + constexpr bool operator>( + const basic_iterator& lhs, const basic_iterator& rhs) + noexcept(noexcept((lhs - rhs) > 0)) { + return (lhs - rhs) > 0; + } + + template + requires cursor::sized_sentinel_for + constexpr bool operator<=( + const basic_iterator& lhs, const basic_iterator& rhs) + noexcept(noexcept((lhs - rhs) <= 0)) { + return (lhs - rhs) <= 0; + } + + template + requires cursor::sized_sentinel_for + constexpr bool operator>=( + const basic_iterator& lhs, const basic_iterator& rhs) + noexcept(noexcept((lhs - rhs) >= 0)) { + return (lhs - rhs) >= 0; + } + + template + class basic_sentinel { + using Base = std::conditional_t; + + public: + std::ranges::sentinel_t end_{}; + basic_sentinel() = default; + constexpr explicit basic_sentinel(std::ranges::sentinel_t end) + : end_{ std::move(end) } {} + + constexpr basic_sentinel(basic_sentinel other) requires Const&& std:: + convertible_to, + std::ranges::sentinel_t> + : end_{ std::move(other.end_) } {} + + constexpr auto end() const { + return end_; + } + + friend class basic_sentinel; + }; + + //tl::compose composes f and g such that compose(f,g)(args...) is f(g(args...)), i.e. g is called first + template + struct compose_fn { + [[no_unique_address]] F f; + [[no_unique_address]] G g; + + template + compose_fn(A&& a, B&& b) : f(std::forward(a)), g(std::forward(b)) {} + + template + static constexpr auto call(A&& a, B&& b, Args&&... args) { + if constexpr (std::is_void_v>) { + std::invoke(std::forward(b), std::forward(args)...); + return std::invoke(std::forward(a)); + } + else { + return std::invoke(std::forward(a), std::invoke(std::forward(b), std::forward(args)...)); + } + } + + template + constexpr auto operator()(Args&&... args) & { + return call(f, g, std::forward(args)...); + } + + template + constexpr auto operator()(Args&&... args) const& { + return call(f, g, std::forward(args)...); + } + + template + constexpr auto operator()(Args&&... args)&& { + return call(std::move(f), std::move(g), std::forward(args)...); + } + + template + constexpr auto operator()(Args&&... args) const&& { + return call(std::move(f), std::move(g), std::forward(args)...); + } + }; + + template + constexpr auto compose(F&& f, G&& g) { + return compose_fn, std::remove_cvref_t>(std::forward(f), std::forward(g)); + } + + //tl::pipeable takes some invocable and enables: + //- Piping a single argument to it such that a | pipeable is the same as pipeable(a) + //- Piping it to another pipeable object, such that a | b is the same as tl::compose(b, a) + struct pipeable_base {}; + template + concept is_pipeable = std::is_base_of_v>; + + template + struct pipeable_fn : pipeable_base { + [[no_unique_address]] F f_; + + constexpr pipeable_fn(F f) : f_(std::move(f)) {} + + template + constexpr auto operator()(Args&&... args) const requires std::invocable { + return std::invoke(f_, std::forward(args)...); + } + }; + + template + constexpr auto pipeable(F f) { + return pipeable_fn{ std::move(f) }; + } + + template + constexpr auto operator|(V&& v, Pipe&& fn) + requires (!is_pipeable && is_pipeable && std::invocable) { + return std::invoke(std::forward(fn).f_, std::forward(v)); + } + + template + constexpr auto operator|(Pipe1&& p1, Pipe2&& p2) + requires (is_pipeable&& is_pipeable) { + return pipeable(compose(std::forward(p2).f_, std::forward(p1).f_)); + } + + //tl::bind_back binds the last N arguments of f to the given ones, returning a new closure + template + constexpr auto bind_back(F&& f, Args&&... args) { + return[f_ = std::forward(f), ...args_ = std::forward(args)] + (auto&&... other_args) + requires std::invocable { + return std::invoke(f_, std::forward(other_args)..., args_...); + }; + } +} + +namespace std { + template + struct iterator_traits> : tl::cursor::associated_types {}; +} + +namespace tl { + + template + constexpr inline std::size_t tuple_size = std::tuple_size_v>; + + template + using index_constant = std::integral_constant; + + namespace meta { + //Partially-apply the given template with the given arguments + template