diff --git a/.circleci/config.yml b/.circleci/config.yml index fc3604d3..42a63179 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,7 +55,14 @@ references: sudo chmod o-w /etc/apt/sources.list.d/clang.list DEBIAN_FRONTEND=noninteractive sudo apt-get update -qq - DEBIAN_FRONTEND=noninteractive sudo apt-get install -y git ccache $APT_COMPILER_PACKAGE cmake make libcurl4-openssl-dev libssl-dev libfuse-dev python + DEBIAN_FRONTEND=noninteractive sudo apt-get install -y git ccache $APT_COMPILER_PACKAGE cmake make libcurl4-openssl-dev libssl-dev libfuse-dev + + # install conan + pyenv global 3.7.0 + python3 -m pip install -U pip + python3 -m pip install conan + source ~/.profile + # Use /dev/urandom when /dev/random is accessed to use less entropy sudo cp -a /dev/urandom /dev/random @@ -67,14 +74,25 @@ references: sudo ln -s /usr/bin/run-clang-tidy-8.py /usr/bin/run-clang-tidy.py fi + # Setup build cache + sudo mkdir -p /build_cache/ccache + sudo mkdir -p /build_cache/conan + sudo chown -R circleci:circleci /build_cache + + # Setup conan cache + echo 'export CONAN_USER_HOME=/build_cache/conan' >> $BASH_ENV + if [[ "${CXX}" == *"g++"* ]]; then + # Conan uses old gcc ABI by default but we can only build with the cxx11 ABI. + conan profile new default --detect + conan profile update settings.compiler.libcxx=libstdc++11 default + fi + # Setup ccache sudo ln -s /usr/bin/ccache /usr/local/bin/$CC sudo ln -s /usr/bin/ccache /usr/local/bin/$CXX - sudo mkdir /ccache_data - sudo chown circleci:circleci /ccache_data echo 'export CCACHE_COMPILERCHECK=content' >> $BASH_ENV echo 'export CCACHE_COMPRESS=1' >> $BASH_ENV - echo 'export CCACHE_DIR=/ccache_data' >> $BASH_ENV + echo 'export CCACHE_DIR=/build_cache/ccache' >> $BASH_ENV echo 'export CCACHE_SLOPPINESS=include_file_mtime' >> $BASH_ENV sudo mkdir -p /tmp/aptcache @@ -88,60 +106,20 @@ references: cmake --version /usr/local/bin/$CC --version /usr/local/bin/$CXX --version - upgrade_boost_pre: &upgrade_boost_pre - restore_cache: - keys: - # Find the most recent cache from any branch - - v5_upgrade_boost_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} - upgrade_boost_post: &upgrade_boost_post - save_cache: - key: v5_upgrade_boost_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} - paths: - - /tmp/boost_1_65_1 - upgrade_boost: &upgrade_boost - run: - name: Upgrade Boost - command: | - # Detect number of CPU cores - export NUMCORES=`nproc` - echo Using $NUMCORES cores - # Download and prepare boost (only if not already present from cache) - if [ ! -d "/tmp/boost_1_65_1" ]; then - echo "Didn't find boost in cache. Downloading and building." - wget -O /tmp/boost.tar.bz2 https://sourceforge.net/projects/boost/files/boost/1.65.1/boost_1_65_1.tar.bz2/download - if [ $(sha512sum /tmp/boost.tar.bz2 | awk '{print $1;}') == "a9e6866d3bb3e7c198f442ff09f5322f58064dca79bc420f2f0168eb63964226dfbc4f034a5a5e5958281fdf7518a1b057c894fbda0b61fced59c1661bf30f1a" ]; then - echo Correct sha512sum - else - echo Wrong sha512sum - sha512sum boost.tar.bz2 - exit 1 - fi - echo Extracting... - tar -xf /tmp/boost.tar.bz2 -C /tmp - rm -rf boost.tar.bz2 - cd /tmp/boost_1_65_1 - ./bootstrap.sh --with-toolset=${BUILD_TOOLSET} --with-libraries=filesystem,thread,chrono,program_options - cd .. - else - echo Found boost in cache. Use cache and build. - fi - # Compile and install boost (if cached, this should be fast) - cd /tmp/boost_1_65_1 - sudo ./b2 toolset=${BUILD_TOOLSET} link=static cxxflags=-fPIC -d0 -j$NUMCORES install build_pre: &build_pre restore_cache: keys: # Find most recent cache from any revision on the same branch (cache keys are prefix matched) # CIRCLE_PR_NUMBER is only set if this is a pull request. - - v4_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }}_{{ .Environment.CIRCLE_PR_NUMBER }} + - v6_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }}_{{ .Environment.CIRCLE_PR_NUMBER }} # Fallback to less specific caches if the one above wasn't found - - v4_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }} - - v4_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} + - v6_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }} + - v6_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} build_post: &build_post save_cache: - key: v4_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }}_{{ .Environment.CIRCLE_PR_NUMBER }}_{{ .Revision }} + key: v6_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }}_{{ .Environment.CIRCLE_PR_NUMBER }}_{{ .Revision }} paths: - - /ccache_data + - /build_cache build: &build run: name: Build @@ -164,6 +142,7 @@ references: # Build mkdir cmake cd cmake + conan install .. -s build_type=${BUILD_TYPE} --build=missing cmake .. -DBUILD_TESTING=on -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${OPENMP_PARAMS} ${CMAKE_FLAGS} make -j$NUMCORES @@ -175,14 +154,14 @@ references: command: | if "${RUN_TESTS}"; then cd cmake - ./test/gitversion/gitversion-test ${GTEST_ARGS} - ./test/cpp-utils/cpp-utils-test ${GTEST_ARGS} - if [ ! "$DISABLE_BROKEN_ASAN_TESTS" = true ] ; then ./test/fspp/fspp-test ${GTEST_ARGS} ; fi - ./test/parallelaccessstore/parallelaccessstore-test ${GTEST_ARGS} - ./test/blockstore/blockstore-test ${GTEST_ARGS} - ./test/blobstore/blobstore-test ${GTEST_ARGS} - ./test/cryfs/cryfs-test ${GTEST_ARGS} - ./test/cryfs-cli/cryfs-cli-test ${GTEST_ARGS} + ./bin/gitversion-test ${GTEST_ARGS} + ./bin/cpp-utils-test ${GTEST_ARGS} + if [ ! "$DISABLE_BROKEN_ASAN_TESTS" = true ] ; then ./bin/fspp-test ${GTEST_ARGS} ; fi + ./bin/parallelaccessstore-test ${GTEST_ARGS} + ./bin/blockstore-test ${GTEST_ARGS} + ./bin/blobstore-test ${GTEST_ARGS} + ./bin/cryfs-test ${GTEST_ARGS} + ./bin/cryfs-cli-test ${GTEST_ARGS} fi job_definition: &job_definition <<: *container_config @@ -191,9 +170,6 @@ references: - <<: *container_setup_pre - <<: *container_setup - <<: *container_setup_post - - <<: *upgrade_boost_pre - - <<: *upgrade_boost - - <<: *upgrade_boost_post - checkout - <<: *build_pre - <<: *build @@ -205,30 +181,6 @@ references: only: /.*/ jobs: - gcc_5_debug: - <<: *job_definition - environment: - CC: gcc-5 - CXX: g++-5 - BUILD_TOOLSET: gcc - APT_COMPILER_PACKAGE: "g++-5" - CXXFLAGS: "" - BUILD_TYPE: "Debug" - GTEST_ARGS: "" - CMAKE_FLAGS: "" - RUN_TESTS: true - gcc_5_release: - <<: *job_definition - environment: - CC: gcc-5 - CXX: g++-5 - BUILD_TOOLSET: gcc - APT_COMPILER_PACKAGE: "g++-5" - CXXFLAGS: "" - BUILD_TYPE: "Release" - GTEST_ARGS: "" - CMAKE_FLAGS: "" - RUN_TESTS: true gcc_6_debug: <<: *job_definition environment: @@ -551,9 +503,6 @@ jobs: - <<: *container_setup_pre - <<: *container_setup - <<: *container_setup_post - - <<: *upgrade_boost_pre - - <<: *upgrade_boost - - <<: *upgrade_boost_post - checkout - run: name: clang-tidy @@ -580,10 +529,6 @@ workflows: build_and_test: jobs: - - gcc_5_debug: - <<: *enable_for_tags - - gcc_5_release: - <<: *enable_for_tags - gcc_6_debug: <<: *enable_for_tags - gcc_6_release: diff --git a/.travisci/build_and_test.sh b/.travisci/build_and_test.sh index deb90903..4ce8ca88 100755 --- a/.travisci/build_and_test.sh +++ b/.travisci/build_and_test.sh @@ -32,19 +32,20 @@ cmake --version # Build echo Build target: ${BUILD_TARGET} +conan install .. -s build_type=${BUILD_TARGET} --build=missing cmake .. -DBUILD_TESTING=on -DCMAKE_BUILD_TYPE=${BUILD_TARGET} make -j$NUMCORES ccache --show-stats # Test -./test/gitversion/gitversion-test -./test/cpp-utils/cpp-utils-test -./test/parallelaccessstore/parallelaccessstore-test -./test/blockstore/blockstore-test -./test/blobstore/blobstore-test -./test/cryfs/cryfs-test +./bin/gitversion-test +./bin/cpp-utils-test +./bin/parallelaccessstore-test +./bin/blockstore-test +./bin/blobstore-test +./bin/cryfs-test # TODO Also run once fixed -# ./test/fspp/fspp-test -# ./test/cryfs-cli/cryfs-cli-test +# ./bin/fspp-test +# ./bin/cryfs-cli-test diff --git a/.travisci/install.sh b/.travisci/install.sh index 3e42c102..7426fcd0 100755 --- a/.travisci/install.sh +++ b/.travisci/install.sh @@ -18,5 +18,7 @@ brew install libomp # By default, travis only fetches the newest 50 commits. We need more in case we're further from the last version tag, so the build doesn't fail because it can't generate the version number. git fetch --unshallow --tags +pip install conan + # Setup ccache brew install ccache diff --git a/CMakeLists.txt b/CMakeLists.txt index e02ebd23..ae72e0c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,20 @@ option(USE_CLANG_TIDY "build with clang-tidy checks enabled" OFF) option(USE_IWYU "build with iwyu checks enabled" OFF) option(CLANG_TIDY_WARNINGS_AS_ERRORS "treat clang-tidy warnings as errors" OFF) +set(CONAN_BUILD_INFO_FILE ${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) + +if(EXISTS ${CONAN_BUILD_INFO_FILE}) + include(${CONAN_BUILD_INFO_FILE}) +else() + message(WARNING "The file ${CONAN_BUILD_INFO_FILE} doesn't exist, you have to run conan install first") +endif() +conan_basic_setup(TARGETS SKIP_STD) + +if(CONAN_SETTINGS_COMPILER_LIBCXX STREQUAL "libstdc++") + # TODO Test this warning works correctly and that the proposed solution in the warning message works. + message(FATAL_ERROR "Conan is set up to build against libstdc++ (i.e. the legacy GCC ABI). We only support libstdc++11 (i.e. the new GCC ABI).\nPlease add the '-s compiler.libcxx=libstdc++11' argument when running 'conan install'.") +endif() + if(USE_IWYU) # note: for iwyu, we need cmake 3.3 cmake_minimum_required(VERSION 3.3 FATAL_ERROR) diff --git a/CMakeSettings.json b/CMakeSettings.json index 5b35c8d4..4b3a7ff4 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -7,7 +7,7 @@ "inheritEnvironments": [ "msvc_x86" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\local\\boost_1_68_0 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", + "cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" }, @@ -18,7 +18,7 @@ "inheritEnvironments": [ "msvc_x86" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\local\\boost_1_68_0 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", + "cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" }, @@ -29,7 +29,7 @@ "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\local\\boost_1_68_0 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", + "cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" }, @@ -40,7 +40,7 @@ "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\local\\boost_1_68_0 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", + "cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" } diff --git a/ChangeLog.txt b/ChangeLog.txt index 8dea146e..18da5a8b 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,12 @@ +Version 0.11.0 (unreleased) +--------------- +Build changes: +* Switch to Conan package manager + +New features: +* Add support for atime mount options (noatime, strictatime, relatime, atime, nodiratime). As before, relatime is the default. + + Version 0.10.2 --------------- Fixed bugs: diff --git a/README.md b/README.md index b5c4030e..758a50a8 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,10 @@ Building from source Requirements ------------ - Git (for getting the source code) - - GCC version >= 5.0 or Clang >= 4.0 + - GCC version >= 6.5 or Clang >= 4.0 - CMake version >= 3.1 + - Conan package manager - libcurl4 (including development headers) - - Boost libraries version >= 1.65.1 (including development headers) - - filesystem - - system - - chrono - - program_options - - thread - SSL development libraries (including development headers, e.g. libssl-dev) - libFUSE version >= 2.8.6 (including development headers), on Mac OS X instead install osxfuse from https://osxfuse.github.io/ - Python >= 2.7 @@ -60,13 +55,16 @@ Requirements You can use the following commands to install these requirements # Ubuntu - $ sudo apt install git g++ cmake make libcurl4-openssl-dev libboost-filesystem-dev libboost-system-dev libboost-chrono-dev libboost-program-options-dev libboost-thread-dev libssl-dev libfuse-dev python + $ sudo apt install git g++ cmake make libcurl4-openssl-dev libssl-dev libfuse-dev python + $ sudo pip install conan # Fedora - sudo dnf install git gcc-c++ cmake make libcurl-devel boost-devel boost-static openssl-devel fuse-devel python + $ sudo dnf install git gcc-c++ cmake make libcurl-devel openssl-devel fuse-devel python + $ sudo pip install conan # Macintosh - brew install cmake boost openssl libomp + $ brew install cmake openssl libomp + $ sudo pip install conan Build & Install --------------- @@ -79,6 +77,7 @@ Build & Install 2. Build $ mkdir cmake && cd cmake + $ conan install .. --build=missing $ cmake .. $ make @@ -97,7 +96,6 @@ Building on Windows (experimental) Build with Visual Studio 2019 and pass in the following flags to CMake: -DDOKAN_PATH=[dokan library location, e.g. "C:\Program Files\Dokan\DokanLibrary-1.2.1"] - -DBOOST_ROOT=[path to root of boost installation] If you set these variables correctly in the `CMakeSettings.json` file, you should be able to open the cryfs source folder with Visual Studio 2019. @@ -106,35 +104,25 @@ Troubleshooting On most systems, CMake should find the libraries automatically. However, that doesn't always work. -1. **Boost headers not found** - - Pass in the boost include path with - - cmake .. -DBoost_INCLUDE_DIRS=/path/to/boost/headers - - If you want to link boost dynamically (e.g. you don't have the static libraries), use the following: - - cmake .. -DBoost_USE_STATIC_LIBS=off - -2. **Fuse/Osxfuse library not found** +1. **Fuse/Osxfuse library not found** Pass in the library path with cmake .. -DFUSE_LIB_PATH=/path/to/fuse/or/osxfuse -3. **Fuse/Osxfuse headers not found** +2. **Fuse/Osxfuse headers not found** Pass in the include path with cmake .. -DCMAKE_CXX_FLAGS="-I/path/to/fuse/or/osxfuse/headers" -4. **Openssl headers not found** +3. **Openssl headers not found** Pass in the include path with cmake .. -DCMAKE_C_FLAGS="-I/path/to/openssl/include" -5. **OpenMP not found (osx)** +4. **OpenMP not found (osx)** Either build it without OpenMP @@ -151,9 +139,11 @@ On most systems, CMake should find the libraries automatically. However, that do Creating .deb and .rpm packages ------------------------------- +It is recommended to install CryFS using packages, because that allows for an easy way to uninstall it again once you don't need it anymore. + There are additional requirements if you want to create packages. They are: - CMake version >= 3.3 - - rpmbuild for creating .rpm package + - rpmbuild if you're creating a .rpm package 1. Clone repository @@ -163,6 +153,7 @@ There are additional requirements if you want to create packages. They are: 2. Build $ mkdir cmake && cd cmake + $ conan install .. --build=missing $ cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=off $ make package diff --git a/appveyor.yml b/appveyor.yml index 46ef3d44..3489469b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ image: -- Visual Studio 2017 -#- Visual Studio 2017 Preview +- Visual Studio 2019 +#- Visual Studio 2019 Preview platform: - x64 @@ -20,29 +20,36 @@ init: - echo %APPVEYOR_BUILD_WORKER_IMAGE% - set arch=32 - if "%PLATFORM%"=="x64" ( set arch=64) -- set VisualStudioVersion=2017 -- if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017 Preview" ( set VisualStudioVersion=Preview) +- set VisualStudioVersion=2019 +- if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2019 Preview" ( set VisualStudioVersion=Preview) - cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\%VisualStudioVersion%\Community\VC\Auxiliary\Build\vcvars%arch%.bat" install: - choco install -y dokany --version 1.2.1.2000 --installargs INSTALLDEVFILES=1 + - pip install conan + - conan --version - cmake --version + - conan profile new default --detect + # note: Conan misdetects our x86 CI platform as x64, fix that + - if "%PLATFORM%"=="x86" ( conan profile update settings.arch=x86 default ) + - if "%PLATFORM%"=="x86" ( conan profile update settings.arch_build=x86 default ) build_script: - cmd: mkdir build - cmd: cd build # note: The cmake+ninja workflow requires us to set build type in both cmake commands ('cmake' and 'cmake --build'), otherwise the cryfs.exe will depend on debug versions of the visual studio c++ runtime (i.e. msvcp140d.dll) - - cmd: cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DBUILD_TESTING=on -DBOOST_ROOT="C:/Libraries/boost_1_67_0" -DDOKAN_PATH="C:/Program Files/Dokan/DokanLibrary-1.2.1" + - cmd: conan install .. -s build_type=%CONFIGURATION% --build=missing + - cmd: cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DBUILD_TESTING=on -DDOKAN_PATH="C:/Program Files/Dokan/DokanLibrary-1.2.1" - cmd: cmake --build . --config %CONFIGURATION% - - cmd: .\test\gitversion\gitversion-test.exe + - cmd: .\bin\gitversion-test.exe # cpp-utils-test disables ThreadDebuggingTest_ThreadName.*_thenIsCorrect because the appveyor image is too old to support the API needed for that - - cmd: .\test\cpp-utils\cpp-utils-test.exe --gtest_filter=-ThreadDebuggingTest_ThreadName.*_thenIsCorrect - #- cmd: .\test\fspp\fspp-test.exe - - cmd: .\test\parallelaccessstore\parallelaccessstore-test.exe - - cmd: .\test\blockstore\blockstore-test.exe - - cmd: .\test\blobstore\blobstore-test.exe - - cmd: .\test\cryfs\cryfs-test.exe - #- cmd: .\test\cryfs-cli\cryfs-cli-test.exe + - cmd: .\bin\cpp-utils-test.exe --gtest_filter=-ThreadDebuggingTest_ThreadName.*_thenIsCorrect + #- cmd: .\bin\fspp-test.exe + - cmd: .\bin\parallelaccessstore-test.exe + - cmd: .\bin\blockstore-test.exe + - cmd: .\bin\blobstore-test.exe + - cmd: .\bin\cryfs-test.exe + #- cmd: .\bin\cryfs-cli-test.exe - cmd: cpack -C %CONFIGURATION% --verbose -G WIX diff --git a/cmake-utils/utils.cmake b/cmake-utils/utils.cmake index da4dff8c..cf591e94 100644 --- a/cmake-utils/utils.cmake +++ b/cmake-utils/utils.cmake @@ -6,22 +6,13 @@ include(CheckCXXCompilerFlag) # Uses: target_activate_cpp14(buildtarget) ################################################### function(target_activate_cpp14 TARGET) - if("${CMAKE_VERSION}" VERSION_GREATER "3.1") + if(MSVC) + # Required by range-v3, see its README.md + set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 17) + else() set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 14) - set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD_REQUIRED ON) - else("${CMAKE_VERSION}" VERSION_GREATER "3.1") - check_cxx_compiler_flag("-std=c++14" COMPILER_HAS_CPP14_SUPPORT) - if (COMPILER_HAS_CPP14_SUPPORT) - target_compile_options(${TARGET} PRIVATE -std=c++14) - else(COMPILER_HAS_CPP14_SUPPORT) - check_cxx_compiler_flag("-std=c++1y" COMPILER_HAS_CPP14_PARTIAL_SUPPORT) - if (COMPILER_HAS_CPP14_PARTIAL_SUPPORT) - target_compile_options(${TARGET} PRIVATE -std=c++1y) - else() - message(FATAL_ERROR "Compiler doesn't support C++14") - endif() - endif(COMPILER_HAS_CPP14_SUPPORT) - endif("${CMAKE_VERSION}" VERSION_GREATER "3.1") + endif() + set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD_REQUIRED ON) # Ideally, we'd like to use libc++ on linux as well, but: # - http://stackoverflow.com/questions/37096062/get-a-basic-c-program-to-compile-using-clang-on-ubuntu-16 # - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=808086 @@ -106,26 +97,8 @@ endfunction(target_enable_style_warnings) # target_add_boost(buildtarget system filesystem) # list all libraries to link against in the dependencies ################################################## function(target_add_boost TARGET) - # Load boost libraries - if(NOT DEFINED Boost_USE_STATIC_LIBS OR Boost_USE_STATIC_LIBS) - # Many supported systems don't have boost >= 1.65.1. Better link it statically. - message(STATUS "Boost will be statically linked") - set(Boost_USE_STATIC_LIBS ON) - else(NOT DEFINED Boost_USE_STATIC_LIBS OR Boost_USE_STATIC_LIBS) - message(STATUS "Boost will be dynamically linked") - set(Boost_USE_STATIC_LIBS OFF) - endif(NOT DEFINED Boost_USE_STATIC_LIBS OR Boost_USE_STATIC_LIBS) - set(BOOST_THREAD_VERSION 4) - find_package(Boost 1.65.1 - REQUIRED - COMPONENTS ${ARGN}) - target_include_directories(${TARGET} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) - target_link_libraries(${TARGET} PUBLIC ${Boost_LIBRARIES}) + target_link_libraries(${TARGET} PUBLIC CONAN_PKG::boost) target_compile_definitions(${TARGET} PUBLIC BOOST_THREAD_VERSION=4) - if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") - # Also link to rt, because boost thread needs that. - target_link_libraries(${TARGET} PUBLIC rt) - endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") endfunction(target_add_boost) ################################################## diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 00000000..89135777 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,40 @@ +from conans import ConanFile, CMake + +class CryFSConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + requires = [ + "range-v3/0.9.1@ericniebler/stable", + "spdlog/1.4.2", + ] + generators = "cmake" + default_options = { + # Need to disable boost-math because that doesn't compile for some reason on CI + "boost:without_math": True, + "boost:without_wave": True, + "boost:without_container": True, + "boost:without_exception": True, + "boost:without_graph": True, + "boost:without_iostreams": True, + "boost:without_locale": True, + "boost:without_log": True, + "boost:without_random": True, + "boost:without_regex": True, + "boost:without_mpi": True, + "boost:without_serialization": True, + "boost:without_coroutine": True, + "boost:without_fiber": True, + "boost:without_context": True, + "boost:without_timer": True, + "boost:without_date_time": True, + "boost:without_atomic": True, + "boost:without_graph_parallel": True, + "boost:without_python": True, + "boost:without_test": True, + "boost:without_type_erasure": True, + } + + def requirements(self): + if self.settings.os == "Windows": + self.requires("boost/1.69.0@conan/stable") + else: + self.requires("boost/1.65.1@conan/stable") diff --git a/run-clang-tidy.sh b/run-clang-tidy.sh index 0b0c5508..d2c55d9f 100755 --- a/run-clang-tidy.sh +++ b/run-clang-tidy.sh @@ -12,6 +12,7 @@ export NUMCORES=`nproc` && if [ ! -n "$NUMCORES" ]; then export NUMCORES=`sysctl echo Using ${NUMCORES} cores # Run cmake in current working directory, but on source that is in the same directory as this script file +conan install .. --build=missing cmake -DBUILD_TESTING=on -DCMAKE_EXPORT_COMPILE_COMMANDS=ON "${0%/*}" # Filter all third party code from the compilation database diff --git a/src/blockstore/implementations/caching/cache/QueueMap.h b/src/blockstore/implementations/caching/cache/QueueMap.h index 851b3265..e541b3ad 100644 --- a/src/blockstore/implementations/caching/cache/QueueMap.h +++ b/src/blockstore/implementations/caching/cache/QueueMap.h @@ -3,6 +3,7 @@ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_QUEUEMAP_H_ #include +#include #include #include #include diff --git a/src/cpp-utils/CMakeLists.txt b/src/cpp-utils/CMakeLists.txt index a0acfd82..a23ff886 100644 --- a/src/cpp-utils/CMakeLists.txt +++ b/src/cpp-utils/CMakeLists.txt @@ -89,8 +89,13 @@ target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_DL_LIBS}) -target_link_libraries(${PROJECT_NAME} PUBLIC spdlog cryptopp) +target_link_libraries(${PROJECT_NAME} PUBLIC CONAN_PKG::spdlog cryptopp CONAN_PKG::range-v3) target_add_boost(${PROJECT_NAME} filesystem system thread chrono) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) + +if(MSVC) + # Required by range-v3, see its README.md + target_compile_options(${PROJECT_NAME} PUBLIC /experimental:preprocessor /permissive- /Zc:twoPhase-) +endif() diff --git a/src/cpp-utils/data/FixedSizeData.h b/src/cpp-utils/data/FixedSizeData.h index 832a96ee..e2938e35 100644 --- a/src/cpp-utils/data/FixedSizeData.h +++ b/src/cpp-utils/data/FixedSizeData.h @@ -4,6 +4,7 @@ #include #include +#include #include #include "../assert/assert.h" diff --git a/src/cryfs-cli/Cli.cpp b/src/cryfs-cli/Cli.cpp index e0d30e90..06ac05d8 100644 --- a/src/cryfs-cli/Cli.cpp +++ b/src/cryfs-cli/Cli.cpp @@ -244,8 +244,7 @@ namespace cryfs_cli { } }; const bool missingBlockIsIntegrityViolation = config.configFile->config()->missingBlockIsIntegrityViolation(); - _device = optional>(make_unique_ref(std::move(config.configFile), std::move(blockStore), std::move(localStateDir), config.myClientId, - options.allowIntegrityViolations(), missingBlockIsIntegrityViolation, std::move(onIntegrityViolation))); + _device = optional>(make_unique_ref(std::move(config.configFile), std::move(blockStore), std::move(localStateDir), config.myClientId, options.allowIntegrityViolations(), missingBlockIsIntegrityViolation, std::move(onIntegrityViolation))); _sanityCheckFilesystem(_device->get()); auto initFilesystem = [&] (fspp::fuse::Fuse *fs){ diff --git a/src/cryfs-cli/program_options/Parser.cpp b/src/cryfs-cli/program_options/Parser.cpp index 192b3d7f..43c18b2a 100644 --- a/src/cryfs-cli/program_options/Parser.cpp +++ b/src/cryfs-cli/program_options/Parser.cpp @@ -81,12 +81,10 @@ ProgramOptions Parser::parse(const vector &supportedCiphers) const { if (vm.count("missing-block-is-integrity-violation")) { missingBlockIsIntegrityViolation = vm["missing-block-is-integrity-violation"].as(); } + if (vm.count("fuse-option")) { auto options = vm["fuse-option"].as>(); for (const auto& option: options) { - if (option == "noatime" || option == "atime") { - LOG(WARN, "CryFS currently doesn't support noatime/atime flags. Using relatime behavior."); - } fuseOptions.push_back("-o"); fuseOptions.push_back(option); } diff --git a/src/cryfs-cli/program_options/ProgramOptions.cpp b/src/cryfs-cli/program_options/ProgramOptions.cpp index fe0d3a80..577ac911 100644 --- a/src/cryfs-cli/program_options/ProgramOptions.cpp +++ b/src/cryfs-cli/program_options/ProgramOptions.cpp @@ -21,7 +21,8 @@ ProgramOptions::ProgramOptions(bf::path baseDir, bf::path mountDir, optional + using std::string; + //TODO Get rid of this in favor of exception hierarchy using fspp::fuse::FuseErrnoException; diff --git a/src/cryfs/impl/filesystem/CryDevice.h b/src/cryfs/impl/filesystem/CryDevice.h index 10dea0e0..a3d59114 100644 --- a/src/cryfs/impl/filesystem/CryDevice.h +++ b/src/cryfs/impl/filesystem/CryDevice.h @@ -15,6 +15,7 @@ #include "cryfs/impl/filesystem/parallelaccessfsblobstore/FileBlobRef.h" #include "cryfs/impl/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.h" + namespace cryfs { class CryDevice final: public fspp::Device { diff --git a/src/cryfs/impl/filesystem/CryDir.cpp b/src/cryfs/impl/filesystem/CryDir.cpp index 6bdef66e..d9cd7424 100644 --- a/src/cryfs/impl/filesystem/CryDir.cpp +++ b/src/cryfs/impl/filesystem/CryDir.cpp @@ -9,7 +9,6 @@ #include "CryFile.h" #include "CryOpenFile.h" #include -#include "cryfs/impl/filesystem/fsblobstore/utils/TimestampUpdateBehavior.h" //TODO Get rid of this in favor of exception hierarchy using fspp::fuse::FuseErrnoException; @@ -72,7 +71,7 @@ vector CryDir::children() { device()->callFsActionCallbacks(); if (!isRootDir()) { // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) //TODO Instead of doing nothing when we're the root directory, handle timestamps in the root dir correctly (and delete isRootDir() function) - parent()->updateAccessTimestampForChild(blockId(), fsblobstore::TimestampUpdateBehavior::RELATIME); + parent()->updateAccessTimestampForChild(blockId(), timestampUpdateBehavior()); } vector children; children.push_back(fspp::Dir::Entry(fspp::Dir::EntryType::DIR, ".")); diff --git a/src/cryfs/impl/filesystem/CryNode.cpp b/src/cryfs/impl/filesystem/CryNode.cpp index b0760900..00f5b69e 100644 --- a/src/cryfs/impl/filesystem/CryNode.cpp +++ b/src/cryfs/impl/filesystem/CryNode.cpp @@ -70,6 +70,10 @@ optional CryNode::grandparent() { return _grandparent->get(); } +fspp::TimestampUpdateBehavior CryNode::timestampUpdateBehavior() const { + return _device->getContext().timestampUpdateBehavior(); +} + void CryNode::rename(const bf::path &to) { device()->callFsActionCallbacks(); if (_parent == none) { diff --git a/src/cryfs/impl/filesystem/CryNode.h b/src/cryfs/impl/filesystem/CryNode.h index f433eb3e..c613ebb9 100644 --- a/src/cryfs/impl/filesystem/CryNode.h +++ b/src/cryfs/impl/filesystem/CryNode.h @@ -16,6 +16,7 @@ public: // TODO grandparent is only needed to set the timestamps of the parent directory on rename and remove. Delete grandparent parameter once we store timestamps in the blob itself instead of in the directory listing. CryNode(CryDevice *device, boost::optional> parent, boost::optional> grandparent, const blockstore::BlockId &blockId); + void access(int mask) const override; stat_info stat() const override; void chmod(fspp::mode_t mode) override; @@ -37,6 +38,7 @@ protected: std::shared_ptr parent() const; std::shared_ptr parent(); boost::optional grandparent(); + fspp::TimestampUpdateBehavior timestampUpdateBehavior() const; virtual fspp::Dir::EntryType getType() const = 0; diff --git a/src/cryfs/impl/filesystem/CryOpenFile.cpp b/src/cryfs/impl/filesystem/CryOpenFile.cpp index 92430088..036e6fac 100644 --- a/src/cryfs/impl/filesystem/CryOpenFile.cpp +++ b/src/cryfs/impl/filesystem/CryOpenFile.cpp @@ -43,7 +43,7 @@ void CryOpenFile::truncate(fspp::num_bytes_t size) const { fspp::num_bytes_t CryOpenFile::read(void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) const { _device->callFsActionCallbacks(); - _parent->updateAccessTimestampForChild(_fileBlob->blockId(), fsblobstore::TimestampUpdateBehavior::RELATIME); + _parent->updateAccessTimestampForChild(_fileBlob->blockId(), timestampUpdateBehavior()); return _fileBlob->read(buf, offset, count); } @@ -64,4 +64,8 @@ void CryOpenFile::fdatasync() { _fileBlob->flush(); } +fspp::TimestampUpdateBehavior CryOpenFile::timestampUpdateBehavior() const { + return _device->getContext().timestampUpdateBehavior(); +} + } diff --git a/src/cryfs/impl/filesystem/CryOpenFile.h b/src/cryfs/impl/filesystem/CryOpenFile.h index 4ef5000b..58f777ab 100644 --- a/src/cryfs/impl/filesystem/CryOpenFile.h +++ b/src/cryfs/impl/filesystem/CryOpenFile.h @@ -21,6 +21,7 @@ public: void flush() override; void fsync() override; void fdatasync() override; + fspp::TimestampUpdateBehavior timestampUpdateBehavior() const; private: const CryDevice *_device; diff --git a/src/cryfs/impl/filesystem/CrySymlink.cpp b/src/cryfs/impl/filesystem/CrySymlink.cpp index 062a486e..5b99ba79 100644 --- a/src/cryfs/impl/filesystem/CrySymlink.cpp +++ b/src/cryfs/impl/filesystem/CrySymlink.cpp @@ -4,7 +4,6 @@ #include "CryDevice.h" #include "CrySymlink.h" #include "cryfs/impl/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.h" -#include "cryfs/impl/filesystem/fsblobstore/utils/TimestampUpdateBehavior.h" //TODO Get rid of this in favor of exception hierarchy @@ -43,7 +42,7 @@ fspp::Dir::EntryType CrySymlink::getType() const { bf::path CrySymlink::target() { device()->callFsActionCallbacks(); - parent()->updateAccessTimestampForChild(blockId(), fsblobstore::TimestampUpdateBehavior::RELATIME); + parent()->updateAccessTimestampForChild(blockId(), timestampUpdateBehavior()); auto blob = LoadBlob(); // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) return blob->target(); } diff --git a/src/cryfs/impl/filesystem/cachingfsblobstore/DirBlobRef.h b/src/cryfs/impl/filesystem/cachingfsblobstore/DirBlobRef.h index 09cc9f9d..3edd0327 100644 --- a/src/cryfs/impl/filesystem/cachingfsblobstore/DirBlobRef.h +++ b/src/cryfs/impl/filesystem/cachingfsblobstore/DirBlobRef.h @@ -4,8 +4,8 @@ #include "FsBlobRef.h" #include "cryfs/impl/filesystem/fsblobstore/DirBlob.h" -#include "cryfs/impl/filesystem/fsblobstore/utils/TimestampUpdateBehavior.h" #include +#include namespace cryfs { namespace cachingfsblobstore { @@ -62,7 +62,7 @@ public: return _base->statChildWithKnownSize(blockId, size); } - void updateAccessTimestampForChild(const blockstore::BlockId &blockId, fsblobstore::TimestampUpdateBehavior timestampUpdateBehavior) { + void updateAccessTimestampForChild(const blockstore::BlockId &blockId, fspp::TimestampUpdateBehavior timestampUpdateBehavior) { return _base->updateAccessTimestampForChild(blockId, timestampUpdateBehavior); } diff --git a/src/cryfs/impl/filesystem/fsblobstore/DirBlob.cpp b/src/cryfs/impl/filesystem/fsblobstore/DirBlob.cpp index 5ce532db..1dc6cb9b 100644 --- a/src/cryfs/impl/filesystem/fsblobstore/DirBlob.cpp +++ b/src/cryfs/impl/filesystem/fsblobstore/DirBlob.cpp @@ -173,7 +173,7 @@ fspp::Node::stat_info DirBlob::statChildWithKnownSize(const BlockId &blockId, fs return result; } -void DirBlob::updateAccessTimestampForChild(const BlockId &blockId, TimestampUpdateBehavior timestampUpdateBehavior) { +void DirBlob::updateAccessTimestampForChild(const BlockId &blockId, fspp::TimestampUpdateBehavior timestampUpdateBehavior) { std::unique_lock lock(_entriesAndChangedMutex); if (_entries.updateAccessTimestampForChild(blockId, timestampUpdateBehavior)) { _changed = true; diff --git a/src/cryfs/impl/filesystem/fsblobstore/DirBlob.h b/src/cryfs/impl/filesystem/fsblobstore/DirBlob.h index f1233882..56bb2fff 100644 --- a/src/cryfs/impl/filesystem/fsblobstore/DirBlob.h +++ b/src/cryfs/impl/filesystem/fsblobstore/DirBlob.h @@ -61,7 +61,7 @@ namespace cryfs { fspp::Node::stat_info statChildWithKnownSize(const blockstore::BlockId &blockId, fspp::num_bytes_t size) const; - void updateAccessTimestampForChild(const blockstore::BlockId &blockId, TimestampUpdateBehavior timestampUpdateBehavior); + void updateAccessTimestampForChild(const blockstore::BlockId &blockId, fspp::TimestampUpdateBehavior timestampUpdateBehavior); void updateModificationTimestampForChild(const blockstore::BlockId &blockId); diff --git a/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.cpp b/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.cpp index 21768605..838138eb 100644 --- a/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.cpp +++ b/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.cpp @@ -228,22 +228,30 @@ void DirEntryList::setAccessTimes(const blockstore::BlockId &blockId, timespec l found->setLastModificationTime(lastModificationTime); } -bool DirEntryList::updateAccessTimestampForChild(const blockstore::BlockId &blockId, TimestampUpdateBehavior timestampUpdateBehavior) { - ASSERT(timestampUpdateBehavior == TimestampUpdateBehavior::RELATIME, "Currently only relatime supported"); +bool DirEntryList::updateAccessTimestampForChild(const blockstore::BlockId &blockId, fspp::TimestampUpdateBehavior timestampUpdateBehavior) { auto found = _findById(blockId); + const timespec lastAccessTime = found->lastAccessTime(); const timespec lastModificationTime = found->lastModificationTime(); const timespec now = cpputils::time::now(); - const timespec yesterday { - /*.tv_sec = */ now.tv_sec - 60*60*24, - /*.tv_nsec = */ now.tv_nsec - }; - bool changed = false; - if (lastAccessTime < lastModificationTime || lastAccessTime < yesterday) { - found->setLastAccessTime(now); - changed = true; + + switch (found->type()) { + case fspp::Dir::EntryType::FILE: + // fallthrough + case fspp::Dir::EntryType::SYMLINK: + if (timestampUpdateBehavior->shouldUpdateATimeOnFileRead(lastAccessTime, lastModificationTime, now)) { + found->setLastAccessTime(now); + return true; + } + return false; + case fspp::Dir::EntryType::DIR: + if (timestampUpdateBehavior->shouldUpdateATimeOnDirectoryRead(lastAccessTime, lastModificationTime, now)) { + found->setLastAccessTime(now); + return true; + } + return false; } - return changed; + throw std::logic_error("Unhandled case"); } void DirEntryList::updateModificationTimestampForChild(const blockstore::BlockId &blockId) { diff --git a/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.h b/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.h index 13e52cc0..8d583c2d 100644 --- a/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.h +++ b/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.h @@ -3,10 +3,10 @@ #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_UTILS_DIRENTRYLIST_H #include +#include #include "DirEntry.h" #include #include -#include "TimestampUpdateBehavior.h" //TODO Address elements by name instead of by blockId when accessing them. Who knows whether there is two hard links for the same blob. @@ -40,7 +40,7 @@ namespace cryfs { void setMode(const blockstore::BlockId &blockId, fspp::mode_t mode); bool setUidGid(const blockstore::BlockId &blockId, fspp::uid_t uid, fspp::gid_t gid); void setAccessTimes(const blockstore::BlockId &blockId, timespec lastAccessTime, timespec lastModificationTime); - bool updateAccessTimestampForChild(const blockstore::BlockId &blockId, TimestampUpdateBehavior timestampUpdateBehavior); + bool updateAccessTimestampForChild(const blockstore::BlockId &blockId, fspp::TimestampUpdateBehavior timestampUpdateBehavior); void updateModificationTimestampForChild(const blockstore::BlockId &blockId); private: diff --git a/src/cryfs/impl/filesystem/fsblobstore/utils/TimestampUpdateBehavior.h b/src/cryfs/impl/filesystem/fsblobstore/utils/TimestampUpdateBehavior.h deleted file mode 100644 index 2c78f8b7..00000000 --- a/src/cryfs/impl/filesystem/fsblobstore/utils/TimestampUpdateBehavior.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#ifndef CRYFS_TIMESTAMPUPDATEBEHAVIOR_H -#define CRYFS_TIMESTAMPUPDATEBEHAVIOR_H - -namespace cryfs { -namespace fsblobstore { - -enum class TimestampUpdateBehavior : uint8_t { - // currently only relatime supported - RELATIME -}; - -} -} - -#endif diff --git a/src/cryfs/impl/filesystem/parallelaccessfsblobstore/DirBlobRef.h b/src/cryfs/impl/filesystem/parallelaccessfsblobstore/DirBlobRef.h index 2ee5dbaf..4daa765f 100644 --- a/src/cryfs/impl/filesystem/parallelaccessfsblobstore/DirBlobRef.h +++ b/src/cryfs/impl/filesystem/parallelaccessfsblobstore/DirBlobRef.h @@ -4,7 +4,6 @@ #include "FsBlobRef.h" #include "cryfs/impl/filesystem/cachingfsblobstore/DirBlobRef.h" -#include "cryfs/impl/filesystem/fsblobstore/utils/TimestampUpdateBehavior.h" #include namespace cryfs { @@ -58,7 +57,7 @@ public: return _base->statChildWithKnownSize(blockId, size); } - void updateAccessTimestampForChild(const blockstore::BlockId &blockId, fsblobstore::TimestampUpdateBehavior timestampUpdateBehavior) { + void updateAccessTimestampForChild(const blockstore::BlockId &blockId, fspp::TimestampUpdateBehavior timestampUpdateBehavior) { return _base->updateAccessTimestampForChild(blockId, timestampUpdateBehavior); } diff --git a/src/fspp/fs_interface/CMakeLists.txt b/src/fspp/fs_interface/CMakeLists.txt index f70cf282..a9ca42bb 100644 --- a/src/fspp/fs_interface/CMakeLists.txt +++ b/src/fspp/fs_interface/CMakeLists.txt @@ -7,7 +7,9 @@ set(SOURCES Node.cpp OpenFile.cpp Symlink.cpp - Types.cpp) + Types.cpp + Context.cpp +) add_library(${PROJECT_NAME} STATIC ${SOURCES}) diff --git a/src/fspp/fs_interface/Context.cpp b/src/fspp/fs_interface/Context.cpp new file mode 100644 index 00000000..9b404b39 --- /dev/null +++ b/src/fspp/fs_interface/Context.cpp @@ -0,0 +1 @@ +#include "Context.h" diff --git a/src/fspp/fs_interface/Context.h b/src/fspp/fs_interface/Context.h new file mode 100644 index 00000000..0e5d786c --- /dev/null +++ b/src/fspp/fs_interface/Context.h @@ -0,0 +1,128 @@ +#pragma once +#ifndef MESSMER_FSPP_FSINTERFACE_CONTEXT_H_ +#define MESSMER_FSPP_FSINTERFACE_CONTEXT_H_ + +#include +#include +#include + +namespace fspp { + +namespace detail { +class TimestampUpdateBehaviorBase { +public: + virtual bool shouldUpdateATimeOnFileRead(timespec oldATime, timespec oldMTime, timespec newATime) const = 0; + virtual bool shouldUpdateATimeOnDirectoryRead(timespec oldATime, timespec oldMTime, timespec newATime) const = 0; +}; +} + +// Defines how atime timestamps of files and directories are accessed on read accesses +// (e.g. atime, strictatime, relatime, nodiratime) +using TimestampUpdateBehavior = std::shared_ptr; + +// atime attribute (of both files and directories) is updated only during write access. +inline TimestampUpdateBehavior noatime() { + class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { + public: + bool shouldUpdateATimeOnFileRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { + return false; + } + bool shouldUpdateATimeOnDirectoryRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { + return false; + } + }; + + static std::shared_ptr singleton = std::make_shared(); + return singleton; +} + +// This causes the atime attribute to update with every file access. (accessing file data, not just the metadata/attributes) +inline TimestampUpdateBehavior strictatime() { + class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { + public: + bool shouldUpdateATimeOnFileRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { + return true; + } + bool shouldUpdateATimeOnDirectoryRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { + return true; + } + }; + + static std::shared_ptr singleton = std::make_shared(); + return singleton; +} + +// This option causes the atime attribute to update only if the previous atime is older than mtime or ctime, or the previous atime is over 24 hours old. +inline TimestampUpdateBehavior relatime() { + // This option causes the atime attribute to update only if the previous atime is older than mtime or ctime, or the previous atime is over 24 hours old. + class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { + public: + bool shouldUpdateATimeOnFileRead(timespec oldATime, timespec oldMTime, timespec newATime) const override { + const timespec yesterday { + /*.tv_sec = */ newATime.tv_sec - 60*60*24, + /*.tv_nsec = */ newATime.tv_nsec + }; + + return oldATime < oldMTime || oldATime < yesterday; + } + bool shouldUpdateATimeOnDirectoryRead(timespec oldATime, timespec oldMTime, timespec newATime) const override { + return shouldUpdateATimeOnFileRead(oldATime, oldMTime, newATime); + } + }; + + static std::shared_ptr singleton = std::make_shared(); + return singleton; +} + +// atime of directories is updated only during write access, can be combined with relatime. atime of files follows the relatime rules. +inline TimestampUpdateBehavior nodiratime_relatime() { + class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { + public: + bool shouldUpdateATimeOnFileRead(timespec oldATime, timespec oldMTime, timespec newATime) const override { + return relatime()->shouldUpdateATimeOnFileRead(oldATime, oldMTime, newATime); + } + bool shouldUpdateATimeOnDirectoryRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { + return false; + } + }; + + static std::shared_ptr singleton = std::make_shared(); + return singleton; +} + +// atime of directories is updated only during write access, can be combined with relatime. atime of files follows the strictatime rules. +inline TimestampUpdateBehavior nodiratime_strictatime() { + class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { + public: + bool shouldUpdateATimeOnFileRead(timespec oldATime, timespec oldMTime, timespec newATime) const override { + return strictatime()->shouldUpdateATimeOnFileRead(oldATime, oldMTime, newATime); + } + bool shouldUpdateATimeOnDirectoryRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { + return false; + } + }; + + static std::shared_ptr singleton = std::make_shared(); + return singleton; +} + +class Context final { +public: + explicit Context(TimestampUpdateBehavior timestampUpdateBehavior) + : _timestampUpdateBehavior(std::move(timestampUpdateBehavior)) {} + + const TimestampUpdateBehavior& timestampUpdateBehavior() const { + return _timestampUpdateBehavior; + } + + void setTimestampUpdateBehavior(TimestampUpdateBehavior value) { + _timestampUpdateBehavior = std::move(value); + } + +private: + TimestampUpdateBehavior _timestampUpdateBehavior; +}; + +} + +#endif diff --git a/src/fspp/fs_interface/Device.h b/src/fspp/fs_interface/Device.h index bf0729e9..807de473 100644 --- a/src/fspp/fs_interface/Device.h +++ b/src/fspp/fs_interface/Device.h @@ -5,6 +5,8 @@ #include #include #include "Types.h" +#include "Context.h" +#include namespace fspp { class Node; @@ -28,6 +30,19 @@ public: virtual boost::optional> LoadDir(const boost::filesystem::path &path) = 0; virtual boost::optional> LoadSymlink(const boost::filesystem::path &path) = 0; + const Context& getContext() const { + ASSERT(_context != boost::none, "Tried to call getContext() but file system isn't running yet."); + return *_context; + } + + // called by fspp system on file system init. Don't call this manually. + // TODO Is there a better way to do this? + void setContext(Context&& context) { + _context = std::move(context); + } + +private: + boost::optional _context; }; } diff --git a/src/fspp/fstest/FsppDeviceTest_Timestamps.h b/src/fspp/fstest/FsppDeviceTest_Timestamps.h index f6d68393..a6adbd7a 100644 --- a/src/fspp/fstest/FsppDeviceTest_Timestamps.h +++ b/src/fspp/fstest/FsppDeviceTest_Timestamps.h @@ -8,30 +8,36 @@ template class FsppDeviceTest_Timestamps: public FsppNodeTest, public TimestampTestUtils { public: void Test_Load_While_Loaded() { - auto node = this->CreateNode("/mynode"); - auto operation = [this] () { - this->device->Load("/mynode"); + auto operation = [this] { + auto node = this->CreateNode("/mynode"); + return [this] { + this->device->Load("/mynode"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } void Test_Load_While_Not_Loaded() { - fspp::Node::stat_info oldStat{}; - { - auto node = this->CreateNode("/mynode"); - oldStat = this->stat(*node); - this->ensureNodeTimestampsAreOld(oldStat); - } + this->testBuilder().withAnyAtimeConfig([&] { + fspp::Node::stat_info oldStat{}; + { + auto node = this->CreateNode("/mynode"); + oldStat = this->stat(*node); + this->ensureNodeTimestampsAreOld(oldStat); + } - this->device->Load("/myfile"); + this->device->Load("/myfile"); - auto node = this->device->Load("/mynode"); + auto node = this->device->Load("/mynode"); - //Test that timestamps didn't change - fspp::Node::stat_info newStat = this->stat(*node.value()); - EXPECT_EQ(oldStat.atime, newStat.atime); - EXPECT_EQ(oldStat.mtime, newStat.mtime); - EXPECT_EQ(oldStat.ctime, newStat.ctime); + //Test that timestamps didn't change + fspp::Node::stat_info newStat = this->stat(*node.value()); + EXPECT_EQ(oldStat.atime, newStat.atime); + EXPECT_EQ(oldStat.mtime, newStat.mtime); + EXPECT_EQ(oldStat.ctime, newStat.ctime); + }); } }; diff --git a/src/fspp/fstest/FsppDirTest_Timestamps.h b/src/fspp/fstest/FsppDirTest_Timestamps.h index 67c0e6d1..c53a7f23 100644 --- a/src/fspp/fstest/FsppDirTest_Timestamps.h +++ b/src/fspp/fstest/FsppDirTest_Timestamps.h @@ -11,225 +11,501 @@ public: TYPED_TEST_SUITE_P(FsppDirTest_Timestamps); TYPED_TEST_P(FsppDirTest_Timestamps, createAndOpenFile) { - auto dir = this->CreateDir("/mydir"); - auto operation = [&dir] () { - dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + return [dir = std::move(dir)] { + dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, createAndOpenFile_inRootDir) { - auto dir = this->LoadDir("/"); - auto operation = [&dir] () { - dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + auto operation = [this] { + auto dir = this->LoadDir("/"); + return [dir = std::move(dir)] { + dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); }*/ TYPED_TEST_P(FsppDirTest_Timestamps, createAndOpenFile_TimestampsOfCreatedFile) { - auto dir = this->CreateDir("/mydir"); - timespec lowerBound = cpputils::time::now(); - dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); - timespec upperBound = cpputils::time::now(); - auto child = this->Load("/mydir/childname"); - this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); - this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); - this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); + this->testBuilder().withAnyAtimeConfig([&] { + auto dir = this->CreateDir("/mydir"); + timespec lowerBound = cpputils::time::now(); + dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + timespec upperBound = cpputils::time::now(); + auto child = this->Load("/mydir/childname"); + this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); + this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); + this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); + }); } TYPED_TEST_P(FsppDirTest_Timestamps, createDir) { - auto dir = this->CreateDir("/mydir"); - auto operation = [&dir] () { - dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + return [dir = std::move(dir)] { + dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, createDir_inRootDir) { - auto dir = this->LoadDir("/"); - auto operation = [&dir] () { - dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + auto operation = [this] { + auto dir = this->LoadDir("/"); + return [dir = std::move(dir)] { + dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); -}*/ + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); +} +*/ TYPED_TEST_P(FsppDirTest_Timestamps, createDir_TimestampsOfCreatedDir) { - auto dir = this->CreateDir("/mydir"); - timespec lowerBound = cpputils::time::now(); - dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); - timespec upperBound = cpputils::time::now(); - auto child = this->Load("/mydir/childname"); - this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); - this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); - this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); + this->testBuilder().withAnyAtimeConfig([&] { + auto dir = this->CreateDir("/mydir"); + timespec lowerBound = cpputils::time::now(); + dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + timespec upperBound = cpputils::time::now(); + auto child = this->Load("/mydir/childname"); + this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); + this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); + this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); + }); } TYPED_TEST_P(FsppDirTest_Timestamps, createSymlink) { - auto dir = this->CreateDir("/mydir"); - auto operation = [&dir] () { - dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + return [dir = std::move(dir)] { + dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, createSymlink_inRootDir) { - auto dir = this->LoadDir("/"); - auto operation = [&dir] () { - dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); + auto operation = [this] { + auto dir = this->LoadDir("/"); + return [dir = std::move(dir)] { + dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); -}*/ + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); +} +*/ TYPED_TEST_P(FsppDirTest_Timestamps, createSymlink_TimestampsOfCreatedSymlink) { - auto dir = this->CreateDir("/mydir"); - timespec lowerBound = cpputils::time::now(); - dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); - timespec upperBound = cpputils::time::now(); - auto child = this->Load("/mydir/childname"); - this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); - this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); - this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); + this->testBuilder().withAnyAtimeConfig([&] { + auto dir = this->CreateDir("/mydir"); + timespec lowerBound = cpputils::time::now(); + dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); + timespec upperBound = cpputils::time::now(); + auto child = this->Load("/mydir/childname"); + this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); + this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); + this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); + }); } -TYPED_TEST_P(FsppDirTest_Timestamps, children_empty) { - auto dir = this->CreateDir("/mydir"); - this->setModificationTimestampLaterThanAccessTimestamp("/mydir"); // to make sure that even in relatime behavior, the read access below changes the access timestamp - auto operation = [&dir] () { - dir->children(); +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeOlderThanMtime_children_empty) { + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + this->setAtimeOlderThanMtime("/mydir"); + return [dir = std::move(dir)] { + dir->children(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} + +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtime_children_empty) { + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + this->setAtimeNewerThanMtime("/mydir"); + return [dir = std::move(dir)] { + dir->children(); + }; + }; + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} + +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_children_empty) { + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + this->setAtimeNewerThanMtimeButBeforeYesterday("/mydir"); + return [dir = std::move(dir)] { + dir->children(); + }; + }; + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } /* TODO Re-enable this test once the root dir handles timestamps correctly -TYPED_TEST_P(FsppDirTest_Timestamps, children_empty_inRootDir) { - auto dir = this->LoadDir("/"); - auto operation = [&dir] () { - dir->children(); +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeOlderThanMtime_children_empty_inRootDir) { + auto operation = [this] { + auto dir = this->LoadDir("/"); + this->setAtimeOlderThanMtime("/"); + return [dir = std::move(dir)] { + dir->children(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); -}*/ + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} -TYPED_TEST_P(FsppDirTest_Timestamps, children_nonempty) { - auto dir = this->CreateDir("/mydir"); - dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); - auto operation = [&dir] () { - dir->children(); +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtime_children_empty_inRootDir) { + auto operation = [this] { + auto dir = this->LoadDir("/"); + this->setAtimeNewerThanMtime("/"); + return [dir = std::move(dir)] { + dir->children(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} + +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_children_empty_inRootDir) { + auto operation = [this] { + auto dir = this->LoadDir("/"); + this->setAtimeNewerThanMtimeButBeforeYesterday("/"); + return [dir = std::move(dir)] { + dir->children(); + }; + }; + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} +*/ + +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeOlderThanMtime_children_nonempty) { + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + this->setAtimeOlderThanMtime("/mydir"); + return [dir = std::move(dir)] { + dir->children(); + }; + }; + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} + +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtime_children_nonempty) { + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + this->setAtimeNewerThanMtime("/mydir"); + return [dir = std::move(dir)] { + dir->children(); + }; + }; + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} + +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_children_nonempty) { + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + this->setAtimeNewerThanMtimeButBeforeYesterday("/mydir"); + return [dir = std::move(dir)] { + dir->children(); + }; + }; + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } /* TODO Re-enable this test once the root dir handles timestamps correctly -TYPED_TEST_P(FsppDirTest_Timestamps, children_nonempty_inRootDir) { - auto dir = this->LoadDir("/"); - dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); - auto operation = [&dir] () { - dir->children(); +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeOlderThanMtime_children_nonempty_inRootDir) { + auto operation = [this] { + auto dir = this->LoadDir("/"); + dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + this->setAtimeOlderThanMtime("/"); + return [dir = std::move(dir)] { + dir->children(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); -}*/ + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} + +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtime_children_nonempty_inRootDir) { + auto operation = [this] { + auto dir = this->LoadDir("/"); + dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + this->setAtimeNewerThanMtime("/"); + return [dir = std::move(dir)] { + dir->children(); + }; + }; + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} + +TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_children_nonempty_inRootDir) { + auto operation = [this] { + auto dir = this->LoadDir("/"); + dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); + this->setAtimeNewerThanMtimeButBeforeYesterday("/"); + return [dir = std::move(dir)] { + dir->children(); + }; + }; + this->testBuilder().withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); +} +*/ template class FsppDirTest_Timestamps_Entries: public FsppNodeTest, public TimestampTestUtils { public: - void Test_deleteChild() { - auto dir = this->CreateDir("/mydir"); - auto child = this->CreateNode("/mydir/childname"); - auto operation = [&child]() { - child->remove(); + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + auto child = this->CreateNode("/mydir/childname"); + return [child = std::move(child)] { + child->remove(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectUpdatesModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } /* TODO Re-enable this test once the root dir handles timestamps correctly void Test_deleteChild_inRootDir() { - auto dir = this->LoadDir("/"); - auto child = this->CreateNode("/childname"); - auto operation = [&child] () { - child->remove(); + auto operation = [this] { + auto dir = this->LoadDir("/"); + auto child = this->CreateNode("/mydir/childname"); + return [child = std::move(child)] { + child->remove(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); - }*/ + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); + } + */ void Test_renameChild() { - auto dir = this->CreateDir("/mydir"); - auto child = this->CreateNode("/mydir/childname"); - auto operation = [&child]() { - child->rename("/mydir/mychild"); + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + auto child = this->CreateNode("/mydir/childname"); + return [child = std::move(child)] { + child->rename("/mydir/mychild"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectUpdatesModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&]{ + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } /* TODO Re-enable this test once the root dir handles timestamps correctly void Test_renameChild_inRootDir() { - auto dir = this->LoadDir("/"); - auto child = this->CreateNode("/childname"); - auto operation = [&child] () { - child->rename("/mydir/mychild"); + auto operation = [this] { + auto dir = this->LoadDir("/"); + auto child = this->CreateNode("/childname"); + return [child = std::move(child)] { + child->rename("/mychild"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); - }*/ + this->testBuilder().withAnyAtimeConfig([&]{ + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); + } + */ void Test_moveChildIn() { - auto sourceDir = this->CreateDir("/sourcedir"); - auto child = this->CreateNode("/sourcedir/childname"); - auto dir = this->CreateDir("/mydir"); - auto operation = [&child]() { - child->rename("/mydir/mychild"); + auto operation = [this] { + auto sourceDir = this->CreateDir("/sourcedir"); + auto child = this->CreateNode("/sourcedir/childname"); + auto dir = this->CreateDir("/mydir"); + return [child = std::move(child)] { + child->rename("/mydir/mychild"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectUpdatesModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } /* TODO Re-enable this test once the root dir handles timestamps correctly void Test_moveChildIn_inRootDir() { - auto sourceDir = this->CreateDir("/sourcedir"); - auto child = this->CreateNode("/sourcedir/childname"); - auto dir = this->LoadDir("/"); - auto operation = [&child] () { - child->rename("/mychild"); + auto operation = [this] { + auto sourceDir = this->CreateDir("/sourcedir"); + auto child = this->CreateNode("/sourcedir/childname"); + auto dir = this->LoadDir("/"); + return [child = std::move(child)] { + child->rename("/mychild"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); - }*/ + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); + } + */ void Test_moveChildOut() { - auto dir = this->CreateDir("/mydir"); - auto child = this->CreateNode("/mydir/childname"); - this->CreateDir("/targetdir"); - auto operation = [&child]() { - child->rename("/targetdir/mychild"); + auto operation = [this] { + auto dir = this->CreateDir("/mydir"); + auto child = this->CreateNode("/mydir/childname"); + this->CreateDir("/targetdir"); + return [child = std::move(child)] { + child->rename("/targetdir/mychild"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectUpdatesModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } /* TODO Re-enable this test once the root dir handles timestamps correctly void Test_moveChildOut_inRootDir() { - auto dir = this->LoadDir("/"); - auto child = this->CreateNode("/childname"); - this->CreateDir("/targetdir"); - auto operation = [&child] () { - child->rename("/targetdir/mychild"); + auto operation = [this] { + auto dir = this->LoadDir("/"); + auto child = this->CreateNode("/childname"); + this->CreateDir("/targetdir"); + return [child = std::move(child)] { + child->rename("/targetdir/mychild"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); - }*/ + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); + } + */ }; REGISTER_TYPED_TEST_SUITE_P(FsppDirTest_Timestamps, @@ -239,8 +515,12 @@ REGISTER_TYPED_TEST_SUITE_P(FsppDirTest_Timestamps, createDir_TimestampsOfCreatedDir, createSymlink, createSymlink_TimestampsOfCreatedSymlink, - children_empty, - children_nonempty + givenAtimeNewerThanMtime_children_empty, + givenAtimeOlderThanMtime_children_empty, + givenAtimeNewerThanMtimeButBeforeYesterday_children_empty, + givenAtimeNewerThanMtime_children_nonempty, + givenAtimeOlderThanMtime_children_nonempty, + givenAtimeNewerThanMtimeButBeforeYesterday_children_nonempty ); REGISTER_NODE_TEST_SUITE(FsppDirTest_Timestamps_Entries, diff --git a/src/fspp/fstest/FsppFileTest_Timestamps.h b/src/fspp/fstest/FsppFileTest_Timestamps.h index f0488c6a..201456df 100644 --- a/src/fspp/fstest/FsppFileTest_Timestamps.h +++ b/src/fspp/fstest/FsppFileTest_Timestamps.h @@ -17,75 +17,111 @@ public: TYPED_TEST_SUITE_P(FsppFileTest_Timestamps); TYPED_TEST_P(FsppFileTest_Timestamps, open_nomode) { - auto file = this->CreateFile("/myfile"); - auto operation = [&file] () { - file->open(fspp::openflags_t(0)); + auto operation = [this] { + auto file = this->CreateFile("/myfile"); + return [file = std::move(file)] { + file->open(fspp::openflags_t(0)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } TYPED_TEST_P(FsppFileTest_Timestamps, open_rdonly) { - auto file = this->CreateFile("/myfile"); - auto operation = [&file] () { - file->open(fspp::openflags_t::RDONLY()); + auto operation = [this] { + auto file = this->CreateFile("/myfile"); + return [file = std::move(file)] { + file->open(fspp::openflags_t::RDONLY()); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } TYPED_TEST_P(FsppFileTest_Timestamps, open_wronly) { - auto file = this->CreateFile("/myfile"); - auto operation = [&file] () { - file->open(fspp::openflags_t::WRONLY()); + auto operation = [this] { + auto file = this->CreateFile("/myfile"); + return [file = std::move(file)] { + file->open(fspp::openflags_t::WRONLY()); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } TYPED_TEST_P(FsppFileTest_Timestamps, open_rdwr) { - auto file = this->CreateFile("/myfile"); - auto operation = [&file] () { - file->open(fspp::openflags_t::RDWR()); + auto operation = [this] { + auto file = this->CreateFile("/myfile"); + return [file = std::move(file)] { + file->open(fspp::openflags_t::RDWR()); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_empty_to_empty) { - auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(0)); - auto operation = [&file] () { - file->truncate(fspp::num_bytes_t(0)); + auto operation = [this] { + auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(0)); + return [file = std::move(file)] { + file->truncate(fspp::num_bytes_t(0)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_empty_to_nonempty) { - auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(0)); - auto operation = [&file] () { - file->truncate(fspp::num_bytes_t(10)); + auto operation = [this] { + auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(0)); + return [file = std::move(file)] { + file->truncate(fspp::num_bytes_t(10)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_nonempty_to_empty) { - auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); - auto operation = [&file] () { - file->truncate(fspp::num_bytes_t(0)); + auto operation = [this] { + auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + return [file = std::move(file)] { + file->truncate(fspp::num_bytes_t(0)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_nonempty_to_nonempty_shrink) { - auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); - auto operation = [&file] () { - file->truncate(fspp::num_bytes_t(5)); + auto operation = [this] { + auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + return [file = std::move(file)] { + file->truncate(fspp::num_bytes_t(5)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_nonempty_to_nonempty_grow) { - auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); - auto operation = [&file] () { - file->truncate(fspp::num_bytes_t(20)); + auto operation = [this] { + auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + return [file = std::move(file)] { + file->truncate(fspp::num_bytes_t(20)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } REGISTER_TYPED_TEST_SUITE_P(FsppFileTest_Timestamps, diff --git a/src/fspp/fstest/FsppNodeTest_Timestamps.h b/src/fspp/fstest/FsppNodeTest_Timestamps.h index f21d2d9f..c08d174b 100644 --- a/src/fspp/fstest/FsppNodeTest_Timestamps.h +++ b/src/fspp/fstest/FsppNodeTest_Timestamps.h @@ -13,339 +13,361 @@ class FsppNodeTest_Timestamps: public FsppNodeTestCreateNode("/mynode"); - timespec upperBound = cpputils::time::now(); - this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *node); - this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *node); - this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *node); + this->testBuilder().withAnyAtimeConfig([&] { + timespec lowerBound = cpputils::time::now(); + auto node = this->CreateNode("/mynode"); + timespec upperBound = cpputils::time::now(); + this->EXPECT_ACCESS_TIMESTAMP_BETWEEN(lowerBound, upperBound, *node); + this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN(lowerBound, upperBound, *node); + this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *node); + }); } void Test_Stat() { - auto node = this->CreateNode("/mynode"); - auto operation = [&node] () { - node->stat(); + auto operation = [this] { + auto node = this->CreateNode("/mynode"); + return [node = std::move(node)] { + node->stat(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { - this->ExpectDoesntUpdateAnyTimestamps + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Chmod() { - auto node = this->CreateNode("/mynode"); - fspp::mode_t mode = this->stat(*node).mode; - auto operation = [&node, mode] () { - node->chmod(mode); + auto operation = [this] { + auto node = this->CreateNode("/mynode"); + fspp::mode_t mode = this->stat(*node).mode; + return [mode, node = std::move(node)] { + node->chmod(mode); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Chown() { - auto node = this->CreateNode("/mynode"); - fspp::uid_t uid = this->stat(*node).uid; - fspp::gid_t gid = this->stat(*node).gid; - auto operation = [&node, uid, gid] () { - node->chown(uid, gid); + auto operation = [this] { + auto node = this->CreateNode("/mynode"); + fspp::uid_t uid = this->stat(*node).uid; + fspp::gid_t gid = this->stat(*node).gid; + return [uid, gid, node = std::move(node)] { + node->chown(uid, gid); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Access() { - auto node = this->CreateNode("/mynode"); - auto operation = [&node] () { - node->access(0); + auto operation = [this] { + auto node = this->CreateNode("/mynode"); + return [node = std::move(node)] { + node->access(0); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { - this->ExpectDoesntUpdateAnyTimestamps + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Error_TargetParentDirDoesntExist() { - auto node = this->CreateNode("/oldname"); - auto operation = [&node] () { - try { - node->rename("/notexistingdir/newname"); - EXPECT_TRUE(false); // expect rename to fail - } catch (const fspp::fuse::FuseErrnoException &e) { - EXPECT_EQ(ENOENT, e.getErrno()); //Rename fails, everything is ok. - } + auto operation = [this] { + auto node = this->CreateNode("/oldname"); + return [node = std::move(node)] { + try { + node->rename("/notexistingdir/newname"); + EXPECT_TRUE(false); // expect rename to fail + } catch (const fspp::fuse::FuseErrnoException &e) { + EXPECT_EQ(ENOENT, e.getErrno()); //Rename fails, everything is ok. + } + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation, { - this->ExpectDoesntUpdateAnyTimestamps + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Error_TargetParentDirIsFile() { - auto node = this->CreateNode("/oldname"); - this->CreateFile("/somefile"); - auto operation = [&node] () { - try { - node->rename("/somefile/newname"); - EXPECT_TRUE(false); // expect rename to fail - } catch (const fspp::fuse::FuseErrnoException &e) { - EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. - } + auto operation = [this] { + auto node = this->CreateNode("/oldname"); + this->CreateFile("/somefile"); + return [node = std::move(node)] { + try { + node->rename("/somefile/newname"); + EXPECT_TRUE(false); // expect rename to fail + } catch (const fspp::fuse::FuseErrnoException &e) { + EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. + } + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation, { - this->ExpectDoesntUpdateAnyTimestamps + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Error_RootDir() { // TODO Re-enable this test once the root dir stores timestamps correctly /* - auto root = this->Load("/"); - auto operation = [&root] () { - try { - root->rename("/newname"); - EXPECT_TRUE(false); // expect throws - } catch (const fspp::fuse::FuseErrnoException &e) { - EXPECT_EQ(EBUSY, e.getErrno()); //Rename fails, everything is ok. - } + auto operation = [this] { + auto root = this->Load("/"); + return [root = std::move(root)] { + try { + root->rename("/newname"); + EXPECT_TRUE(false); // expect throws + } catch (const fspp::fuse::FuseErrnoException &e) { + EXPECT_EQ(EBUSY, e.getErrno()); //Rename fails, everything is ok. + } + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { - this->ExpectDoesntUpdateAnyTimestamps - }); - */ + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }); + */ } void Test_Rename_InRoot() { - auto node = this->CreateNode("/oldname"); - auto operation = [&node] () { - node->rename("/newname"); + auto operation = [this] { + auto node = this->CreateNode("/oldname"); + return [node = std::move(node)] { + node->rename("/newname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/newname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_InNested() { - this->CreateDir("/mydir"); - auto node = this->CreateNode("/mydir/oldname"); - auto operation = [&node] () { - node->rename("/mydir/newname"); + auto operation = [this] { + this->CreateDir("/mydir"); + auto node = this->CreateNode("/mydir/oldname"); + return [node = std::move(node)] { + node->rename("/mydir/newname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/mydir/newname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/mydir/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_RootToNested_SameName() { - this->CreateDir("/mydir"); - auto node = this->CreateNode("/oldname"); - auto operation = [&node] () { - node->rename("/mydir/oldname"); + auto operation = [this] { + this->CreateDir("/mydir"); + auto node = this->CreateNode("/oldname"); + return [node = std::move(node)] { + node->rename("/mydir/oldname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/mydir/oldname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/mydir/oldname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_RootToNested_NewName() { - this->CreateDir("/mydir"); - auto node = this->CreateNode("/oldname"); - auto operation = [&node] () { - node->rename("/mydir/newname"); + auto operation = [this] { + this->CreateDir("/mydir"); + auto node = this->CreateNode("/oldname"); + return [node = std::move(node)] { + node->rename("/mydir/newname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/mydir/newname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/mydir/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_NestedToRoot_SameName() { - this->CreateDir("/mydir"); - auto node = this->CreateNode("/mydir/oldname"); - auto operation = [&node] () { - node->rename("/oldname"); + auto operation = [this] { + this->CreateDir("/mydir"); + auto node = this->CreateNode("/mydir/oldname"); + return [node = std::move(node)] { + node->rename("/oldname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/oldname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/oldname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_NestedToRoot_NewName() { - this->CreateDir("/mydir"); - auto node = this->CreateNode("/mydir/oldname"); - auto operation = [&node] () { - node->rename("/newname"); + auto operation = [this] { + this->CreateDir("/mydir"); + auto node = this->CreateNode("/mydir/oldname"); + return [node = std::move(node)] { + node->rename("/newname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/newname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_NestedToNested_SameName() { - this->CreateDir("/mydir1"); - this->CreateDir("/mydir2"); - auto node = this->CreateNode("/mydir1/oldname"); - auto operation = [&node] () { - node->rename("/mydir2/oldname"); + auto operation = [this] { + this->CreateDir("/mydir1"); + this->CreateDir("/mydir2"); + auto node = this->CreateNode("/mydir1/oldname"); + return [node = std::move(node)] { + node->rename("/mydir2/oldname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/oldname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/oldname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_NestedToNested_NewName() { - this->CreateDir("/mydir1"); - this->CreateDir("/mydir2"); - auto node = this->CreateNode("/mydir1/oldname"); - auto operation = [&node] () { - node->rename("/mydir2/newname"); + auto operation = [this] { + this->CreateDir("/mydir1"); + this->CreateDir("/mydir2"); + auto node = this->CreateNode("/mydir1/oldname"); + return [node = std::move(node)] { + node->rename("/mydir2/newname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/newname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_ToItself() { - auto node = this->CreateNode("/oldname"); - auto operation = [&node] () { - node->rename("/oldname"); + auto operation = [this] { + auto node = this->CreateNode("/oldname"); + return [node = std::move(node)] { + node->rename("/oldname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/oldname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/oldname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_Overwrite_InSameDir() { - auto node = this->CreateNode("/oldname"); - this->CreateNode("/newname"); - auto operation = [&node] () { - node->rename("/newname"); + auto operation = [this] { + auto node = this->CreateNode("/oldname"); + this->CreateNode("/newname"); + return [node = std::move(node)] { + node->rename("/newname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/newname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_Overwrite_InDifferentDir() { - this->CreateDir("/mydir1"); - this->CreateDir("/mydir2"); - this->CreateNode("/mydir2/newname"); - auto node = this->CreateNode("/mydir1/oldname"); - auto operation = [&node] () { - node->rename("/mydir2/newname"); + auto operation = [this] { + this->CreateDir("/mydir1"); + this->CreateDir("/mydir2"); + this->CreateNode("/mydir2/newname"); + auto node = this->CreateNode("/mydir1/oldname"); + return [node = std::move(node)] { + node->rename("/mydir2/newname"); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/newname", operation, { - this->ExpectDoesntUpdateAccessTimestamp, - this->ExpectDoesntUpdateModificationTimestamp, - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_Overwrite_Error_DirWithFile_InSameDir() { - this->CreateFile("/oldname"); - this->CreateDir("/newname"); - auto node = this->Load("/oldname"); - auto operation = [&node] () { - try { - node->rename("/newname"); - EXPECT_TRUE(false); // expect rename to fail - } catch (const fspp::fuse::FuseErrnoException &e) { - EXPECT_EQ(EISDIR, e.getErrno()); //Rename fails, everything is ok. - } + auto operation = [this] { + this->CreateFile("/oldname"); + this->CreateDir("/newname"); + auto node = this->Load("/oldname"); + return [node = std::move(node)] { + try { + node->rename("/newname"); + EXPECT_TRUE(false); // expect rename to fail + } catch (const fspp::fuse::FuseErrnoException &e) { + EXPECT_EQ(EISDIR, e.getErrno()); //Rename fails, everything is ok. + } + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation, { - this->ExpectDoesntUpdateAnyTimestamps + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Overwrite_Error_DirWithFile_InDifferentDir() { - this->CreateDir("/mydir1"); - this->CreateDir("/mydir2"); - this->CreateFile("/mydir1/oldname"); - this->CreateDir("/mydir2/newname"); - auto node = this->Load("/mydir1/oldname"); - auto operation = [&node] () { - try { - node->rename("/mydir2/newname"); - EXPECT_TRUE(false); // expect rename to fail - } catch (const fspp::fuse::FuseErrnoException &e) { - EXPECT_EQ(EISDIR, e.getErrno());//Rename fails, everything is ok. - } + auto operation = [this] { + this->CreateDir("/mydir1"); + this->CreateDir("/mydir2"); + this->CreateFile("/mydir1/oldname"); + this->CreateDir("/mydir2/newname"); + auto node = this->Load("/mydir1/oldname"); + return [node = std::move(node)] { + try { + node->rename("/mydir2/newname"); + EXPECT_TRUE(false); // expect rename to fail + } catch (const fspp::fuse::FuseErrnoException &e) { + EXPECT_EQ(EISDIR, e.getErrno());//Rename fails, everything is ok. + } + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", operation, { - this->ExpectDoesntUpdateAnyTimestamps + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Overwrite_Error_FileWithDir_InSameDir() { - this->CreateDir("/oldname"); - this->CreateFile("/newname"); - auto node = this->Load("/oldname"); - auto operation = [&node] () { - try { - node->rename("/newname"); - EXPECT_TRUE(false); // expect rename to fail - } catch (const fspp::fuse::FuseErrnoException &e) { - EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. - } + auto operation = [this] { + this->CreateDir("/oldname"); + this->CreateFile("/newname"); + auto node = this->Load("/oldname"); + return [node = std::move(node)] { + try { + node->rename("/newname"); + EXPECT_TRUE(false); // expect rename to fail + } catch (const fspp::fuse::FuseErrnoException &e) { + EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. + } + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation, { - this->ExpectDoesntUpdateAnyTimestamps + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Overwrite_Error_FileWithDir_InDifferentDir() { - this->CreateDir("/mydir1"); - this->CreateDir("/mydir2"); - this->CreateDir("/mydir1/oldname"); - this->CreateFile("/mydir2/newname"); - auto node = this->Load("/mydir1/oldname"); - auto operation = [&node] () { - try { - node->rename("/mydir2/newname"); - EXPECT_TRUE(false); // expect rename to fail - } catch (const fspp::fuse::FuseErrnoException &e) { - EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. - } + auto operation = [this] { + this->CreateDir("/mydir1"); + this->CreateDir("/mydir2"); + this->CreateDir("/mydir1/oldname"); + this->CreateFile("/mydir2/newname"); + auto node = this->Load("/mydir1/oldname"); + return [node = std::move(node)] { + try { + node->rename("/mydir2/newname"); + EXPECT_TRUE(false); // expect rename to fail + } catch (const fspp::fuse::FuseErrnoException &e) { + EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. + } + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", operation, { - this->ExpectDoesntUpdateAnyTimestamps + this->testBuilder().withAnyAtimeConfig([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Utimens() { - auto node = this->CreateNode("/mynode"); - timespec atime = this->xSecondsAgo(100); - timespec mtime = this->xSecondsAgo(200); - auto operation = [&node, atime, mtime] () { - node->utimens(atime, mtime); - }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { - this->ExpectUpdatesMetadataTimestamp + this->testBuilder().withAnyAtimeConfig([&] { + auto node = this->CreateNode("/mynode"); + timespec atime = this->xSecondsAgo(100); + timespec mtime = this->xSecondsAgo(200); + auto operation = [atime, mtime, &node] { + node->utimens(atime, mtime); + }; + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { + this->ExpectUpdatesMetadataTimestamp + }); + EXPECT_EQ(atime, this->stat(*node).atime); + EXPECT_EQ(mtime, this->stat(*node).mtime); }); - EXPECT_EQ(atime, this->stat(*node).atime); - EXPECT_EQ(mtime, this->stat(*node).mtime); } }; diff --git a/src/fspp/fstest/FsppOpenFileTest_Timestamps.h b/src/fspp/fstest/FsppOpenFileTest_Timestamps.h index 7ade3455..663e66a2 100644 --- a/src/fspp/fstest/FsppOpenFileTest_Timestamps.h +++ b/src/fspp/fstest/FsppOpenFileTest_Timestamps.h @@ -14,120 +14,343 @@ public: auto file = this->CreateFile(path); file->truncate(size); auto openFile = file->open(fspp::openflags_t::RDWR()); - assert(this->stat(*openFile).size == size); - assert(this->stat(*this->Load(path)).size == size); + ASSERT(this->stat(*openFile).size == size, ""); + ASSERT(this->stat(*this->Load(path)).size == size, ""); + return openFile; + } + void CreateFileWithSize(const boost::filesystem::path &path, fspp::num_bytes_t size) { + auto file = this->CreateFile(path); + file->truncate(size); + } + cpputils::unique_ref OpenFile(const boost::filesystem::path &path, fspp::num_bytes_t size) { + auto file = this->LoadFile(path); + auto openFile = file->open(fspp::openflags_t::RDWR()); + ASSERT(this->stat(*openFile).size == size, ""); + ASSERT(this->stat(*this->Load(path)).size == size, ""); return openFile; } }; TYPED_TEST_SUITE_P(FsppOpenFileTest_Timestamps); TYPED_TEST_P(FsppOpenFileTest_Timestamps, stat) { - auto openFile = this->CreateAndOpenFile("/mynode"); - auto operation = [&openFile] () { - openFile->stat(); + auto operation = [] (fspp::OpenFile* openFile){ + return [openFile] { + openFile->stat(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFile("/mynode"); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_empty_to_empty) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); - auto operation = [&openFile] () { - openFile->truncate(fspp::num_bytes_t(0)); + auto operation = [] (fspp::OpenFile* openFile){ + return [openFile] { + openFile->truncate(fspp::num_bytes_t(0)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_empty_to_nonempty) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); - auto operation = [&openFile] () { - openFile->truncate(fspp::num_bytes_t(10)); + auto operation = [] (fspp::OpenFile* openFile){ + return [openFile] { + openFile->truncate(fspp::num_bytes_t(10)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_empty) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); - auto operation = [&openFile] () { - openFile->truncate(fspp::num_bytes_t(0)); + auto operation = [] (fspp::OpenFile* openFile){ + return [openFile] { + openFile->truncate(fspp::num_bytes_t(0)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_nonempty_shrink) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); - auto operation = [&openFile] () { - openFile->truncate(fspp::num_bytes_t(5)); + auto operation = [] (fspp::OpenFile* openFile){ + return [openFile] { + openFile->truncate(fspp::num_bytes_t(5)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_nonempty_grow) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); - auto operation = [&openFile] () { - openFile->truncate(fspp::num_bytes_t(20)); + auto operation = [] (fspp::OpenFile* openFile){ + return [openFile] { + openFile->truncate(fspp::num_bytes_t(20)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } -TYPED_TEST_P(FsppOpenFileTest_Timestamps, read_inbounds) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); - auto operation = [&openFile] () { - std::array buffer{}; - openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(0)); +TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_read_inbounds) { + auto operation = [this] () { + this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->setAtimeNewerThanMtimeButBeforeYesterday("/myfile"); + auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); + auto* openFilePtr = openFile.get(); + + return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { + std::array buffer{}; + openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(0)); + }); }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + this->testBuilder() + .withNoatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); } -TYPED_TEST_P(FsppOpenFileTest_Timestamps, read_outofbounds) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); - auto operation = [&openFile] () { - std::array buffer{}; - openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(2)); +TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeNewerThanMtime_read_inbounds) { + auto operation = [this] () { + this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->setAtimeNewerThanMtime("/myfile"); + auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); + auto* openFilePtr = openFile.get(); + + return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { + std::array buffer{}; + openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(0)); + }); }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + this->testBuilder() + .withNoatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); +} + +TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeOlderThanMtime_read_inbounds) { + auto operation = [this] () { + this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->setAtimeOlderThanMtime("/myfile"); + auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); + auto* openFilePtr = openFile.get(); + + return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { + std::array buffer{}; + openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(0)); + }); + }; + this->testBuilder() + .withNoatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); +} + +TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_read_outofbounds) { + auto operation = [this] () { + this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->setAtimeNewerThanMtimeButBeforeYesterday("/myfile"); + auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); + auto* openFilePtr = openFile.get(); + + return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { + std::array buffer{}; + openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(2)); + }); + }; + this->testBuilder() + .withNoatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); +} + +TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeNewerThanMtime_read_outofbounds) { + auto operation = [this] () { + this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->setAtimeNewerThanMtime("/myfile"); + auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); + auto* openFilePtr = openFile.get(); + + return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { + std::array buffer{}; + openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(2)); + }); + }; + this->testBuilder() + .withNoatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); +} + +TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeOlderThanMtime_read_outofbounds) { + auto operation = [this] () { + this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->setAtimeOlderThanMtime("/myfile"); + auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); + auto* openFilePtr = openFile.get(); + + return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { + std::array buffer{}; + openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(2)); + }); + }; + this->testBuilder() + .withNoatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeStrictatime([&] { + auto op = operation(); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, write_inbounds) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); - auto operation = [&openFile] () { - openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); + auto operation = [] (fspp::OpenFile* openFile){ + return [openFile] { + openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, write_outofbounds) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); - auto operation = [&openFile] () { - openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(2)); + auto operation = [] (fspp::OpenFile* openFile){ + return [openFile] { + openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(2)); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, flush) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); - openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); - auto operation = [&openFile] () { - openFile->flush(); + auto operation = [] (fspp::OpenFile* openFile){ + openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); + return [openFile] { + openFile->flush(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, fsync) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); - openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); - auto operation = [&openFile] () { - openFile->fsync(); + auto operation = [] (fspp::OpenFile* openFile){ + openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); + return [openFile] { + openFile->fsync(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, fdatasync) { - auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); - openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); - auto operation = [&openFile] () { - openFile->fdatasync(); + auto operation = [] (fspp::OpenFile* openFile){ + openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); + return [openFile] { + openFile->fdatasync(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAnyTimestamps}); + this->testBuilder().withAnyAtimeConfig([&] { + auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAnyTimestamps}); + }); } REGISTER_TYPED_TEST_SUITE_P(FsppOpenFileTest_Timestamps, @@ -137,8 +360,12 @@ REGISTER_TYPED_TEST_SUITE_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_empty, truncate_nonempty_to_nonempty_shrink, truncate_nonempty_to_nonempty_grow, - read_inbounds, - read_outofbounds, + givenAtimeNewerThanMtimeButBeforeYesterday_read_inbounds, + givenAtimeNewerThanMtime_read_inbounds, + givenAtimeOlderThanMtime_read_inbounds, + givenAtimeNewerThanMtimeButBeforeYesterday_read_outofbounds, + givenAtimeNewerThanMtime_read_outofbounds, + givenAtimeOlderThanMtime_read_outofbounds, write_inbounds, write_outofbounds, flush, diff --git a/src/fspp/fstest/FsppSymlinkTest_Timestamps.h b/src/fspp/fstest/FsppSymlinkTest_Timestamps.h index 6796ed00..c6fc2d12 100644 --- a/src/fspp/fstest/FsppSymlinkTest_Timestamps.h +++ b/src/fspp/fstest/FsppSymlinkTest_Timestamps.h @@ -10,17 +10,76 @@ public: }; TYPED_TEST_SUITE_P(FsppSymlinkTest_Timestamps); -TYPED_TEST_P(FsppSymlinkTest_Timestamps, target) { - auto symlink = this->CreateSymlink("/mysymlink"); - this->setModificationTimestampLaterThanAccessTimestamp("/mysymlink"); // to make sure that even in relatime behavior, the read access below changes the access timestamp - auto operation = [&symlink] () { - symlink->target(); +TYPED_TEST_P(FsppSymlinkTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_target) { + auto operation = [this] { + auto symlink = this->CreateSymlink("/mysymlink"); + this->setAtimeNewerThanMtimeButBeforeYesterday("/mysymlink"); + return [symlink = std::move(symlink)] { + symlink->target(); + }; }; - this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + this->testBuilder() + .withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); +} + +TYPED_TEST_P(FsppSymlinkTest_Timestamps, givenAtimeOlderThanMtime_target) { + auto operation = [this] { + auto symlink = this->CreateSymlink("/mysymlink"); + this->setAtimeOlderThanMtime("/mysymlink"); + return [symlink = std::move(symlink)] { + symlink->target(); + }; + }; + this->testBuilder() + .withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); +} + +TYPED_TEST_P(FsppSymlinkTest_Timestamps, givenAtimeNewerThanMtime_target) { + auto operation = [this] { + auto symlink = this->CreateSymlink("/mysymlink"); + this->setAtimeNewerThanMtime("/mysymlink"); + return [symlink = std::move(symlink)] { + symlink->target(); + }; + }; + this->testBuilder() + .withNoatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }).withRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeRelatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); + }).withNodiratimeStrictatime([&] { + this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); + }); } REGISTER_TYPED_TEST_SUITE_P(FsppSymlinkTest_Timestamps, - target + givenAtimeNewerThanMtimeButBeforeYesterday_target, + givenAtimeNewerThanMtime_target, + givenAtimeOlderThanMtime_target ); #endif diff --git a/src/fspp/fstest/testutils/FileSystemTest.h b/src/fspp/fstest/testutils/FileSystemTest.h index 35090266..81b7599e 100644 --- a/src/fspp/fstest/testutils/FileSystemTest.h +++ b/src/fspp/fstest/testutils/FileSystemTest.h @@ -30,10 +30,20 @@ public: "Given test fixture for instantiating the (type parameterized) FileSystemTest must inherit from FileSystemTestFixture" ); - FileSystemTest(): fixture(), device(fixture.createDevice()) {} + FileSystemTest(): fixture(nullptr), device(nullptr) { + resetFilesystem(fspp::Context{fspp::relatime()}); + } - ConcreteFileSystemTestFixture fixture; - cpputils::unique_ref device; + void resetFilesystem(fspp::Context&& context) { + device = nullptr; + fixture = nullptr; + fixture = std::make_unique(); + device = fixture->createDevice(); + device->setContext(std::move(context)); + } + + std::unique_ptr fixture; + std::unique_ptr device; static constexpr fspp::mode_t MODE_PUBLIC = fspp::mode_t() .addUserReadFlag().addUserWriteFlag().addUserExecFlag() @@ -91,15 +101,41 @@ public: EXPECT_NE(nullptr, dynamic_cast(node.get())); } - void setModificationTimestampLaterThanAccessTimestamp(const boost::filesystem::path& path) { + void setAtimeOlderThanMtime(const boost::filesystem::path& path) { auto node = device->Load(path).value(); auto st = node->stat(); - st.mtime.tv_nsec = st.mtime.tv_nsec + 1; + st.atime.tv_nsec = st.mtime.tv_nsec - 1; node->utimens( st.atime, st.mtime ); } + + void setAtimeNewerThanMtime(const boost::filesystem::path& path) { + auto node = device->Load(path).value(); + auto st = node->stat(); + st.atime.tv_nsec = st.mtime.tv_nsec + 1; + node->utimens( + st.atime, + st.mtime + ); + } + + void setAtimeNewerThanMtimeButBeforeYesterday(const boost::filesystem::path& path) { + auto node = device->Load(path).value(); + auto st = node->stat(); + const timespec now = cpputils::time::now(); + const timespec before_yesterday { + /*.tv_sec = */ now.tv_sec - 60*60*24 - 1, + /*.tv_nsec = */ now.tv_nsec + }; + st.atime = before_yesterday; + st.mtime.tv_nsec = st.atime.tv_nsec - 1; + node->utimens( + st.atime, + st.mtime + ); + } }; template constexpr fspp::mode_t FileSystemTest::MODE_PUBLIC; diff --git a/src/fspp/fstest/testutils/TimestampTestUtils.h b/src/fspp/fstest/testutils/TimestampTestUtils.h index 42751726..82d46b34 100644 --- a/src/fspp/fstest/testutils/TimestampTestUtils.h +++ b/src/fspp/fstest/testutils/TimestampTestUtils.h @@ -10,17 +10,22 @@ template class TimestampTestUtils : public virtual FileSystemTest { public: - using TimestampUpdateBehavior = std::function; + using TimestampUpdateExpectation = std::function; - static TimestampUpdateBehavior ExpectUpdatesAccessTimestamp; - static TimestampUpdateBehavior ExpectDoesntUpdateAccessTimestamp; - static TimestampUpdateBehavior ExpectUpdatesModificationTimestamp; - static TimestampUpdateBehavior ExpectDoesntUpdateModificationTimestamp; - static TimestampUpdateBehavior ExpectUpdatesMetadataTimestamp; - static TimestampUpdateBehavior ExpectDoesntUpdateMetadataTimestamp; - static TimestampUpdateBehavior ExpectDoesntUpdateAnyTimestamps; + static TimestampUpdateExpectation ExpectUpdatesAccessTimestamp; + static TimestampUpdateExpectation ExpectDoesntUpdateAccessTimestamp; + static TimestampUpdateExpectation ExpectUpdatesModificationTimestamp; + static TimestampUpdateExpectation ExpectDoesntUpdateModificationTimestamp; + static TimestampUpdateExpectation ExpectUpdatesMetadataTimestamp; + static TimestampUpdateExpectation ExpectDoesntUpdateMetadataTimestamp; + static TimestampUpdateExpectation ExpectDoesntUpdateAnyTimestamps; - void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(std::function statOld, std::function statNew, std::function operation, std::initializer_list behaviorChecks) { + void setTimestampUpdateBehavior(fspp::TimestampUpdateBehavior timestampUpdateBehavior) { + FileSystemTest::device->setContext(fspp::Context { timestampUpdateBehavior }); + } + + template + void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(std::function statOld, std::function statNew, Operation&& operation, std::initializer_list behaviorChecks) { auto oldStat = statOld(); ensureNodeTimestampsAreOld(oldStat); timespec timeBeforeOperation = cpputils::time::now(); @@ -32,26 +37,29 @@ public: } } - void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const fspp::OpenFile &node, std::function operation, std::initializer_list behaviorChecks) { + template + void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const fspp::OpenFile &node, Operation&& operation, std::initializer_list behaviorChecks) { EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS( [this, &node](){return this->stat(node);}, [this, &node](){return this->stat(node);}, - operation, + std::forward(operation), behaviorChecks ); } - void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const boost::filesystem::path &oldPath, const boost::filesystem::path &newPath, std::function operation, std::initializer_list behaviorChecks) { + template + void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const boost::filesystem::path &oldPath, const boost::filesystem::path &newPath, Operation&& operation, std::initializer_list behaviorChecks) { EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS( [this, oldPath](){return this->stat(*this->Load(oldPath));}, [this, newPath](){return this->stat(*this->Load(newPath));}, - operation, + std::forward(operation), behaviorChecks ); } - void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const boost::filesystem::path &path, std::function operation, std::initializer_list behaviorChecks) { - EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(path, path, operation, behaviorChecks); + template + void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const boost::filesystem::path &path, Operation&& operation, std::initializer_list behaviorChecks) { + EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(path, path, std::forward(operation), behaviorChecks); } void EXPECT_ACCESS_TIMESTAMP_BETWEEN(timespec lowerBound, timespec upperBound, const fspp::Node &node) { @@ -90,6 +98,55 @@ public: EXPECT_LT(nodeStat.ctime, cpputils::time::now()); } + class TestBuilder final { + public: + explicit TestBuilder(TimestampTestUtils* fixture): _fixture(fixture) {} + + const TestBuilder& withNoatime(std::function expectations) const { + _fixture->resetFilesystem(fspp::Context {fspp::noatime()}); + expectations(); + return *this; + } + + const TestBuilder& withStrictatime(std::function expectations) const { + _fixture->resetFilesystem(fspp::Context {fspp::strictatime()}); + expectations(); + return *this; + } + + const TestBuilder& withRelatime(std::function expectations) const { + _fixture->resetFilesystem(fspp::Context {fspp::relatime()}); + expectations(); + return *this; + } + + const TestBuilder& withNodiratimeRelatime(std::function expectations) const { + _fixture->resetFilesystem(fspp::Context {fspp::nodiratime_relatime()}); + expectations(); + return *this; + } + + const TestBuilder& withNodiratimeStrictatime(std::function expectations) const { + _fixture->resetFilesystem(fspp::Context {fspp::nodiratime_strictatime()}); + expectations(); + return *this; + } + + const TestBuilder& withAnyAtimeConfig(std::function expectations) const { + return withNoatime(expectations) + .withStrictatime(expectations) + .withRelatime(expectations) + .withNodiratimeRelatime(expectations) + .withNodiratimeStrictatime(expectations); + } + + private: + TimestampTestUtils* _fixture; + }; + TestBuilder testBuilder() { + return TestBuilder(this); + } + private: void waitUntilClockProgresses() { @@ -101,7 +158,7 @@ private: }; template -typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectUpdatesAccessTimestamp = +typename TimestampTestUtils::TimestampUpdateExpectation TimestampTestUtils::ExpectUpdatesAccessTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(statBeforeOperation); UNUSED(timeBeforeOperation); @@ -111,7 +168,7 @@ typename TimestampTestUtils::TimestampUpdateBehav }; template -typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectDoesntUpdateAccessTimestamp = +typename TimestampTestUtils::TimestampUpdateExpectation TimestampTestUtils::ExpectDoesntUpdateAccessTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(timeBeforeOperation); UNUSED(timeAfterOperation); @@ -119,7 +176,7 @@ typename TimestampTestUtils::TimestampUpdateBehav }; template -typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectUpdatesModificationTimestamp = +typename TimestampTestUtils::TimestampUpdateExpectation TimestampTestUtils::ExpectUpdatesModificationTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(statBeforeOperation); EXPECT_LE(timeBeforeOperation, statAfterOperation.mtime); @@ -127,7 +184,7 @@ typename TimestampTestUtils::TimestampUpdateBehav }; template -typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectDoesntUpdateModificationTimestamp = +typename TimestampTestUtils::TimestampUpdateExpectation TimestampTestUtils::ExpectDoesntUpdateModificationTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(timeBeforeOperation); UNUSED(timeAfterOperation); @@ -135,7 +192,7 @@ typename TimestampTestUtils::TimestampUpdateBehav }; template -typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectUpdatesMetadataTimestamp = +typename TimestampTestUtils::TimestampUpdateExpectation TimestampTestUtils::ExpectUpdatesMetadataTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(statBeforeOperation); EXPECT_LE(timeBeforeOperation, statAfterOperation.ctime); @@ -143,7 +200,7 @@ typename TimestampTestUtils::TimestampUpdateBehav }; template -typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectDoesntUpdateMetadataTimestamp = +typename TimestampTestUtils::TimestampUpdateExpectation TimestampTestUtils::ExpectDoesntUpdateMetadataTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(timeBeforeOperation); UNUSED(timeAfterOperation); @@ -151,7 +208,7 @@ typename TimestampTestUtils::TimestampUpdateBehav }; template -typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectDoesntUpdateAnyTimestamps = +typename TimestampTestUtils::TimestampUpdateExpectation TimestampTestUtils::ExpectDoesntUpdateAnyTimestamps = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { ExpectDoesntUpdateAccessTimestamp(statBeforeOperation, statAfterOperation, timeBeforeOperation, timeAfterOperation); ExpectDoesntUpdateModificationTimestamp(statBeforeOperation, statAfterOperation, timeBeforeOperation, timeAfterOperation); diff --git a/src/fspp/fuse/CMakeLists.txt b/src/fspp/fuse/CMakeLists.txt index b991bd72..81c07c40 100644 --- a/src/fspp/fuse/CMakeLists.txt +++ b/src/fspp/fuse/CMakeLists.txt @@ -39,6 +39,7 @@ elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(CMAKE_FIND_FRAMEWORK LAST) find_library_with_path(FUSE "osxfuse" FUSE_LIB_PATH) target_link_libraries(${PROJECT_NAME} PUBLIC ${FUSE}) + target_include_directories(${PROJECT_NAME} PUBLIC /usr/local/include/osxfuse/) else() # Linux find_library_with_path(FUSE "fuse" FUSE_LIB_PATH) target_link_libraries(${PROJECT_NAME} PUBLIC ${FUSE}) diff --git a/src/fspp/fuse/Filesystem.h b/src/fspp/fuse/Filesystem.h index 241ee621..eb20d1f8 100644 --- a/src/fspp/fuse/Filesystem.h +++ b/src/fspp/fuse/Filesystem.h @@ -6,6 +6,7 @@ #include #include #include "../fs_interface/Dir.h" +#include "../fs_interface/Context.h" #if defined(_MSC_VER) #include #else @@ -19,6 +20,8 @@ class Filesystem { public: virtual ~Filesystem() {} + virtual void setContext(Context&& context) = 0; + //TODO Test uid/gid parameters of createAndOpenFile virtual int createAndOpenFile(const boost::filesystem::path &path, ::mode_t mode, ::uid_t uid, ::gid_t gid) = 0; virtual int openFile(const boost::filesystem::path &path, int flags) = 0; diff --git a/src/fspp/fuse/Fuse.cpp b/src/fspp/fuse/Fuse.cpp index 96faeec8..970aeb74 100644 --- a/src/fspp/fuse/Fuse.cpp +++ b/src/fspp/fuse/Fuse.cpp @@ -13,6 +13,10 @@ #include "InvalidFilesystem.h" #include +#include +#include +#include + #if defined(_MSC_VER) #include #include @@ -265,19 +269,19 @@ void Fuse::_logUnknownException() { LOG(ERR, "Unknown exception thrown"); } -void Fuse::runInForeground(const bf::path &mountdir, const vector &fuseOptions) { - vector realFuseOptions = fuseOptions; +void Fuse::runInForeground(const bf::path &mountdir, vector fuseOptions) { + vector realFuseOptions = std::move(fuseOptions); if (std::find(realFuseOptions.begin(), realFuseOptions.end(), "-f") == realFuseOptions.end()) { realFuseOptions.push_back("-f"); } - _run(mountdir, realFuseOptions); + _run(mountdir, std::move(realFuseOptions)); } -void Fuse::runInBackground(const bf::path &mountdir, const vector &fuseOptions) { - vector realFuseOptions = fuseOptions; +void Fuse::runInBackground(const bf::path &mountdir, vector fuseOptions) { + vector realFuseOptions = std::move(fuseOptions); _removeAndWarnIfExists(&realFuseOptions, "-f"); _removeAndWarnIfExists(&realFuseOptions, "-d"); - _run(mountdir, realFuseOptions); + _run(mountdir, std::move(realFuseOptions)); } void Fuse::_removeAndWarnIfExists(vector *fuseOptions, const std::string &option) { @@ -291,7 +295,52 @@ void Fuse::_removeAndWarnIfExists(vector *fuseOptions, const std::string } } -void Fuse::_run(const bf::path &mountdir, const vector &fuseOptions) { +namespace { +void extractAllAtimeOptionsAndRemoveOnesUnknownToLibfuse_(string* csv_options, vector* result) { + const auto is_fuse_supported_atime_flag = [] (const std::string& flag) { + constexpr std::array flags = {"noatime", "atime"}; + return flags.end() != std::find(flags.begin(), flags.end(), flag); + }; + const auto is_fuse_unsupported_atime_flag = [] (const std::string& flag) { + constexpr std::array flags = {"strictatime", "relatime", "nodiratime"}; + return flags.end() != std::find(flags.begin(), flags.end(), flag); + }; + *csv_options = *csv_options + | ranges::view::split(',') + | ranges::view::filter( + [&] (const std::string& elem) { + if (is_fuse_unsupported_atime_flag(elem)) { + result->push_back(elem); + return false; + } + if (is_fuse_supported_atime_flag(elem)) { + result->push_back(elem); + } + return true; + }) + | ranges::view::join(',') + | ranges::to(); +} + +// Return a list of all atime options (e.g. atime, noatime, relatime, strictatime, nodiratime) that occur in the +// fuseOptions input. They must be preceded by a '-o', i.e. {..., '-o', 'noatime', ...} and multiple ones can be +// csv-concatenated, i.e. {..., '-o', 'atime,nodiratime', ...}. +// Also, this function removes all of these atime options that are unknown to libfuse (i.e. all except atime and noatime) +// from the input fuseOptions so we can pass it on to libfuse without crashing. +vector extractAllAtimeOptionsAndRemoveOnesUnknownToLibfuse_(vector* fuseOptions) { + vector result; + bool lastOptionWasDashO = false; + for (string& option : *fuseOptions) { + if (lastOptionWasDashO) { + extractAllAtimeOptionsAndRemoveOnesUnknownToLibfuse_(&option, &result); + } + lastOptionWasDashO = (option == "-o"); + } + return result; +} +} + +void Fuse::_run(const bf::path &mountdir, vector fuseOptions) { #if defined(__GLIBC__)|| defined(__APPLE__) || defined(_MSC_VER) // Avoid encoding errors for non-utf8 characters, see https://github.com/cryfs/cryfs/issues/247 // this is ifdef'd out for non-glibc linux, because musl doesn't handle this correctly. @@ -302,11 +351,66 @@ void Fuse::_run(const bf::path &mountdir, const vector &fuseOptions) { ASSERT(_argv.size() == 0, "Filesystem already started"); + vector atimeOptions = extractAllAtimeOptionsAndRemoveOnesUnknownToLibfuse_(&fuseOptions); + _createContext(atimeOptions); + _argv = _build_argv(mountdir, fuseOptions); fuse_main(_argv.size(), _argv.data(), operations(), this); } +void Fuse::_createContext(const vector &fuseOptions) { + const bool has_atime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "atime"); + const bool has_noatime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "noatime"); + const bool has_relatime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "relatime"); + const bool has_strictatime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "strictatime"); + const bool has_nodiratime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "nodiratime"); + + // Default is RELATIME + _context = Context(relatime()); + + if (has_noatime_flag) { + ASSERT(!has_atime_flag, "Cannot have both, noatime and atime flags set."); + ASSERT(!has_relatime_flag, "Cannot have both, noatime and relatime flags set."); + ASSERT(!has_strictatime_flag, "Cannot have both, noatime and strictatime flags set."); + // note: can have nodiratime flag set but that is ignored because it is already included in the noatime policy. + _context->setTimestampUpdateBehavior(noatime()); + } else if (has_relatime_flag) { + // note: can have atime and relatime both set, they're identical + ASSERT(!has_noatime_flag, "This shouldn't happen, or we would have hit a case above."); + ASSERT(!has_strictatime_flag, "Cannot have both, relatime and strictatime flags set."); + if (has_nodiratime_flag) { + _context->setTimestampUpdateBehavior(nodiratime_relatime()); + } else { + _context->setTimestampUpdateBehavior(relatime()); + } + } else if (has_atime_flag) { + // note: can have atime and relatime both set, they're identical + ASSERT(!has_noatime_flag, "This shouldn't happen, or we would have hit a case above"); + ASSERT(!has_strictatime_flag, "Cannot have both, atime and strictatime flags set."); + if (has_nodiratime_flag) { + _context->setTimestampUpdateBehavior(nodiratime_relatime()); + } else { + _context->setTimestampUpdateBehavior(relatime()); + } + } else if (has_strictatime_flag) { + ASSERT(!has_noatime_flag, "This shouldn't happen, or we would have hit a case above"); + ASSERT(!has_atime_flag, "This shouldn't happen, or we would have hit a case above"); + ASSERT(!has_relatime_flag, "This shouldn't happen, or we would have hit a case above"); + if (has_nodiratime_flag) { + _context->setTimestampUpdateBehavior(nodiratime_strictatime()); + } else { + _context->setTimestampUpdateBehavior(strictatime()); + } + } else if (has_nodiratime_flag) { + ASSERT(!has_noatime_flag, "This shouldn't happen, or we would have hit a case above"); + ASSERT(!has_atime_flag, "This shouldn't happen, or we would have hit a case above"); + ASSERT(!has_relatime_flag, "This shouldn't happen, or we would have hit a case above"); + ASSERT(!has_strictatime_flag, "This shouldn't happen, or we would have hit a case above"); + _context->setTimestampUpdateBehavior(nodiratime_relatime()); // use relatime by default + } +} + vector Fuse::_build_argv(const bf::path &mountdir, const vector &fuseOptions) { vector argv; argv.reserve(6 + fuseOptions.size()); // fuseOptions + executable name + mountdir + 2x fuse options (subtype, fsname), each taking 2 entries ("-o", "key=value"). @@ -1100,6 +1204,9 @@ void Fuse::init(fuse_conn_info *conn) { ThreadNameForDebugging _threadName("init"); _fs = _init(this); + ASSERT(_context != boost::none, "Context should have been initialized in Fuse::run() but somehow didn't"); + _fs->setContext(fspp::Context { *_context }); + LOG(INFO, "Filesystem started."); _running = true; diff --git a/src/fspp/fuse/Fuse.h b/src/fspp/fuse/Fuse.h index 1a0c07c6..2dd3c2d4 100644 --- a/src/fspp/fuse/Fuse.h +++ b/src/fspp/fuse/Fuse.h @@ -12,6 +12,7 @@ #include #include #include "stat_compatibility.h" +#include namespace fspp { class Device; @@ -24,8 +25,8 @@ public: explicit Fuse(std::function (Fuse *fuse)> init, std::function onMounted, std::string fstype, boost::optional fsname); ~Fuse(); - void runInBackground(const boost::filesystem::path &mountdir, const std::vector &fuseOptions); - void runInForeground(const boost::filesystem::path &mountdir, const std::vector &fuseOptions); + void runInBackground(const boost::filesystem::path &mountdir, std::vector fuseOptions); + void runInForeground(const boost::filesystem::path &mountdir, std::vector fuseOptions); bool running() const; void stop(); @@ -67,11 +68,12 @@ private: static void _logUnknownException(); static char *_create_c_string(const std::string &str); static void _removeAndWarnIfExists(std::vector *fuseOptions, const std::string &option); - void _run(const boost::filesystem::path &mountdir, const std::vector &fuseOptions); + void _run(const boost::filesystem::path &mountdir, std::vector fuseOptions); static bool _has_option(const std::vector &vec, const std::string &key); static bool _has_entry_with_prefix(const std::string &prefix, const std::vector &vec); std::vector _build_argv(const boost::filesystem::path &mountdir, const std::vector &fuseOptions); void _add_fuse_option_if_not_exists(std::vector *argv, const std::string &key, const std::string &value); + void _createContext(const std::vector &fuseOptions); std::function (Fuse *fuse)> _init; std::function _onMounted; @@ -81,6 +83,7 @@ private: std::atomic _running; std::string _fstype; boost::optional _fsname; + boost::optional _context; DISALLOW_COPY_AND_ASSIGN(Fuse); }; diff --git a/src/fspp/fuse/InvalidFilesystem.h b/src/fspp/fuse/InvalidFilesystem.h index 6c91d5e0..f39a2254 100644 --- a/src/fspp/fuse/InvalidFilesystem.h +++ b/src/fspp/fuse/InvalidFilesystem.h @@ -7,6 +7,10 @@ namespace fspp { namespace fuse { class InvalidFilesystem final : public Filesystem { + void setContext(Context&&) override { + throw std::logic_error("Filesystem not initialized yet"); + } + int createAndOpenFile(const boost::filesystem::path &, ::mode_t , ::uid_t , ::gid_t ) override { throw std::logic_error("Filesystem not initialized yet"); } diff --git a/src/fspp/fuse/params.h b/src/fspp/fuse/params.h index 4a45ef79..9903ac82 100644 --- a/src/fspp/fuse/params.h +++ b/src/fspp/fuse/params.h @@ -3,14 +3,6 @@ #define MESSMER_FSPP_FUSE_PARAMS_H_ #define FUSE_USE_VERSION 26 -#if defined(__linux__) || defined(__FreeBSD__) #include -#elif __APPLE__ -#include -#elif defined(_MSC_VER) -#include // Dokany fuse -#else -#error System not supported -#endif #endif diff --git a/src/fspp/impl/FilesystemImpl.cpp b/src/fspp/impl/FilesystemImpl.cpp index 71c1ca91..d7afb837 100644 --- a/src/fspp/impl/FilesystemImpl.cpp +++ b/src/fspp/impl/FilesystemImpl.cpp @@ -91,6 +91,10 @@ FilesystemImpl::~FilesystemImpl() { #endif } +void FilesystemImpl::setContext(Context&& context) { + _device->setContext(std::move(context)); +} + unique_ref FilesystemImpl::LoadFile(const bf::path &path) { PROFILE(_loadFileNanosec); auto file = _device->LoadFile(path); diff --git a/src/fspp/impl/FilesystemImpl.h b/src/fspp/impl/FilesystemImpl.h index 8c20d395..9ff43f24 100644 --- a/src/fspp/impl/FilesystemImpl.h +++ b/src/fspp/impl/FilesystemImpl.h @@ -24,6 +24,8 @@ public: explicit FilesystemImpl(cpputils::unique_ref device); virtual ~FilesystemImpl(); + void setContext(Context&& context) override; + int openFile(const boost::filesystem::path &path, int flags) override; void flush(int descriptor) override; void closeFile(int descriptor) override; diff --git a/test/cpp-utils/assert/backtrace_test.cpp b/test/cpp-utils/assert/backtrace_test.cpp index a5b46709..3a94870a 100644 --- a/test/cpp-utils/assert/backtrace_test.cpp +++ b/test/cpp-utils/assert/backtrace_test.cpp @@ -91,7 +91,7 @@ TEST(BacktraceTest, ContainsBacktrace) { } TEST(BacktraceTest, ShowBacktraceOnNullptrAccess) { - auto output = call_process_exiting_with_nullptr_violation(); + auto output = call_process_exiting_with_nullptr_violation(); #if defined(_MSC_VER) EXPECT_THAT(output, HasSubstr("handle_exit_signal")); #else @@ -120,7 +120,7 @@ TEST(BacktraceTest, ShowBacktraceOnUnhandledException) { TEST(BacktraceTest, ShowBacktraceOnSigIll) { auto output = call_process_exiting_with_sigill(); #if defined(_MSC_VER) - EXPECT_THAT(output, HasSubstr("handle_exit_signal")); + EXPECT_THAT(output, HasSubstr("handle_exit_signal")); #else EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); #endif @@ -128,7 +128,7 @@ TEST(BacktraceTest, ShowBacktraceOnSigIll) { #else TEST(BacktraceTest, ContainsBacktrace) { string backtrace = cpputils::backtrace(); - EXPECT_THAT(backtrace, HasSubstr("#1")); + EXPECT_THAT(backtrace, HasSubstr("#0")); } TEST(BacktraceTest, ShowBacktraceOnNullptrAccess) { auto output = call_process_exiting_with_nullptr_violation(); @@ -153,8 +153,8 @@ TEST(BacktraceTest, ShowBacktraceOnSigIll) { #if !defined(_MSC_VER) TEST(BacktraceTest, ShowBacktraceOnSigAbrt) { - auto output = call_process_exiting_with_sigabrt(); - EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); + auto output = call_process_exiting_with_sigabrt(); + EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); } TEST(BacktraceTest, ShowBacktraceOnSigAbrt_ShowsCorrectSignalName) { diff --git a/test/cryfs-cli/program_options/ProgramOptionsTest.cpp b/test/cryfs-cli/program_options/ProgramOptionsTest.cpp index d556aa00..84ed1cf3 100644 --- a/test/cryfs-cli/program_options/ProgramOptionsTest.cpp +++ b/test/cryfs-cli/program_options/ProgramOptionsTest.cpp @@ -137,4 +137,3 @@ TEST_F(ProgramOptionsTest, SomeFuseOptions) { //Fuse should have the mount dir as first parameter EXPECT_VECTOR_EQ({"-f", "--longoption"}, testobj.fuseOptions()); } - diff --git a/test/cryfs/impl/filesystem/CryFsTest.cpp b/test/cryfs/impl/filesystem/CryFsTest.cpp index cd92e79f..0de82e1b 100644 --- a/test/cryfs/impl/filesystem/CryFsTest.cpp +++ b/test/cryfs/impl/filesystem/CryFsTest.cpp @@ -65,8 +65,10 @@ auto failOnIntegrityViolation() { TEST_F(CryFsTest, CreatedRootdirIsLoadableAfterClosing) { { CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); + dev.setContext(fspp::Context {fspp::relatime()}); } CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); + dev.setContext(fspp::Context {fspp::relatime()}); auto rootDir = dev.LoadDir(bf::path("/")); rootDir.value()->children(); } @@ -74,10 +76,12 @@ TEST_F(CryFsTest, CreatedRootdirIsLoadableAfterClosing) { TEST_F(CryFsTest, LoadingFilesystemDoesntModifyConfigFile) { { CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); + dev.setContext(fspp::Context {fspp::relatime()}); } Data configAfterCreating = Data::LoadFromFile(config.path()).value(); { CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); + dev.setContext(fspp::Context {fspp::relatime()}); } Data configAfterLoading = Data::LoadFromFile(config.path()).value(); EXPECT_EQ(configAfterCreating, configAfterLoading); diff --git a/test/cryfs/impl/filesystem/testutils/CryTestBase.h b/test/cryfs/impl/filesystem/testutils/CryTestBase.h index aac26005..7eb91928 100644 --- a/test/cryfs/impl/filesystem/testutils/CryTestBase.h +++ b/test/cryfs/impl/filesystem/testutils/CryTestBase.h @@ -20,6 +20,7 @@ public: CryTestBase(): _tempLocalStateDir(), _localStateDir(_tempLocalStateDir.path()), _configFile(false), _device(nullptr) { auto fakeBlockStore = cpputils::make_unique_ref(); _device = std::make_unique(configFile(), std::move(fakeBlockStore), _localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); + _device->setContext(fspp::Context { fspp::relatime() }); } std::shared_ptr configFile() { diff --git a/test/fspp/CMakeLists.txt b/test/fspp/CMakeLists.txt index dabff1f1..c6f2e32c 100644 --- a/test/fspp/CMakeLists.txt +++ b/test/fspp/CMakeLists.txt @@ -69,6 +69,7 @@ set(SOURCES fuse/access/FuseAccessModeTest.cpp fuse/access/FuseAccessErrorTest.cpp fuse/BasicFuseTest.cpp + fuse/TimestampTest.cpp fuse/rmdir/testutils/FuseRmdirTest.cpp fuse/rmdir/FuseRmdirErrorTest.cpp fuse/rmdir/FuseRmdirDirnameTest.cpp diff --git a/test/fspp/fuse/TimestampTest.cpp b/test/fspp/fuse/TimestampTest.cpp new file mode 100644 index 00000000..71e5dc43 --- /dev/null +++ b/test/fspp/fuse/TimestampTest.cpp @@ -0,0 +1,313 @@ +#include "../testutils/FuseTest.h" + +#include + +using namespace fspp::fuse; + +typedef FuseTest FuseTimestampTest; + +// Single flag + +TEST_F(FuseTimestampTest, whenCalledWithoutAnyAtimeFlag_thenHasRelatimeBehavior) { + auto fs = TestFS({}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeFlag_thenHasNoatimeBehavior) { + auto fs = TestFS({"-o", "noatime"}); + EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeFlag_thenHasStrictatimeBehavior) { + auto fs = TestFS({"-o", "strictatime"}); + EXPECT_EQ(fspp::strictatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeFlag_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "relatime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeFlag_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "atime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeFlag_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + + + +// Flag combinations + +TEST_F(FuseTimestampTest, whenCalledWithAtimeAtimeFlag_withCsv_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "atime,atime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeAtimeFlag_withSeparateFlags_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "atime", "-o", "atime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeNoatimeFlag_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "atime,noatime"}), + "Cannot have both, noatime and atime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeNoatimeFlag_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "atime", "-o", "noatime"}), + "Cannot have both, noatime and atime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeRelatimeFlag_withCsv_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "atime,relatime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeRelatimeFlag_withSeparateFlags_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "atime", "-o", "relatime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeStrictatimeFlag_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "atime,strictatime"}), + "Cannot have both, atime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeStrictatimeFlag_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "atime", "-o", "strictatime"}), + "Cannot have both, atime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeNodiratimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "atime,nodiratime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithAtimeNodiratimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "atime", "-o", "nodiratime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeAtime_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "noatime,atime"}), + "Cannot have both, noatime and atime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeAtime_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "noatime", "-o", "atime"}), + "Cannot have both, noatime and atime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeNoatimeFlag_withCsv_thenHasNoatimeBehavior) { + auto fs = TestFS({"-o", "noatime,noatime"}); + EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeNoatimeFlag_withSeparateFlags_thenHasNoatimeBehavior) { + auto fs = TestFS({"-o", "noatime", "-o", "noatime"}); + EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeRelatime_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "noatime,relatime"}), + "Cannot have both, noatime and relatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeRelatime_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "noatime", "-o", "relatime"}), + "Cannot have both, noatime and relatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeStrictatime_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "noatime,strictatime"}), + "Cannot have both, noatime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeStrictatime_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "noatime", "-o", "strictatime"}), + "Cannot have both, noatime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeNodiratimeFlag_withCsv_thenHasNoatimeBehavior) { + auto fs = TestFS({"-o", "noatime,nodiratime"}); + EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNoatimeNodiratimeFlag_withSeparateFlags_thenHasNoatimeBehavior) { + auto fs = TestFS({"-o", "noatime", "-o", "nodiratime"}); + EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeAtimeFlag_withCsv_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "relatime,atime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeAtimeFlag_withSeparateFlags_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "relatime", "-o", "atime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeNoatime_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "relatime,noatime"}), + "Cannot have both, noatime and relatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeNoatime_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "relatime", "-o", "noatime"}), + "Cannot have both, noatime and relatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeRelatimeFlag_withCsv_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "relatime,relatime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeRelatimeFlag_withSeparateFlags_thenHasRelatimeBehavior) { + auto fs = TestFS({"-o", "relatime", "-o", "relatime"}); + EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeStrictatime_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "relatime,strictatime"}), + "Cannot have both, relatime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeStrictatime_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "relatime", "-o", "strictatime"}), + "Cannot have both, relatime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeNodiratimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "relatime,nodiratime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithRelatimeNodiratimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "relatime", "-o", "nodiratime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeAtimeFlag_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "strictatime,atime"}), + "Cannot have both, atime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeAtimeFlag_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "strictatime", "-o", "atime"}), + "Cannot have both, atime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeNoatimeFlag_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "strictatime,noatime"}), + "Cannot have both, noatime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeNoatimeFlag_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "strictatime", "-o", "noatime"}), + "Cannot have both, noatime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeRelatimeFlag_withCsv_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "strictatime,relatime"}), + "Cannot have both, relatime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeRelatimeFlag_withSeparateFlags_thenFails) { + EXPECT_DEATH( + TestFS({"-o", "strictatime", "-o", "relatime"}), + "Cannot have both, relatime and strictatime flags set."); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeStrictatimeFlag_withCsv_thenHasStrictatimeBehavior) { + auto fs = TestFS({"-o", "strictatime,strictatime"}); + EXPECT_EQ(fspp::strictatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeStrictatimeFlag_withSeparateFlags_thenHasStrictatimeBehavior) { + auto fs = TestFS({"-o", "strictatime", "-o", "strictatime"}); + EXPECT_EQ(fspp::strictatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeNodiratimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "strictatime,nodiratime"}); + EXPECT_EQ(fspp::nodiratime_strictatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithStrictatimeNodiratimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "strictatime", "-o", "nodiratime"}); + EXPECT_EQ(fspp::nodiratime_strictatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeAtimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime,atime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeAtimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime", "-o", "atime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeNoatimeFlag_withCsv_thenHasNoatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime,noatime"}); + EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeNoatimeFlag_withSeparateFlags_thenHasNoatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime", "-o", "noatime"}); + EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeRelatimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime,relatime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeRelatimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime", "-o", "relatime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeStrictatimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime,strictatime"}); + EXPECT_EQ(fspp::nodiratime_strictatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeStrictatimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime", "-o", "strictatime"}); + EXPECT_EQ(fspp::nodiratime_strictatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeNodiratimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime,nodiratime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} + +TEST_F(FuseTimestampTest, whenCalledWithNodiratimeNodiratimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { + auto fs = TestFS({"-o", "nodiratime", "-o", "nodiratime"}); + EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); +} diff --git a/test/fspp/testutils/FuseTest.cpp b/test/fspp/testutils/FuseTest.cpp index 7ae9cd39..fed165a2 100644 --- a/test/fspp/testutils/FuseTest.cpp +++ b/test/fspp/testutils/FuseTest.cpp @@ -19,7 +19,7 @@ using namespace fspp::fuse; MockFilesystem::MockFilesystem() {} MockFilesystem::~MockFilesystem() {} -FuseTest::FuseTest(): fsimpl(make_shared()) { +FuseTest::FuseTest(): fsimpl(make_shared()), _context(boost::none) { auto defaultAction = Throw(FuseErrnoException(EIO)); ON_CALL(*fsimpl, openFile(_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, closeFile(_)).WillByDefault(defaultAction); @@ -48,18 +48,23 @@ FuseTest::FuseTest(): fsimpl(make_shared()) { ON_CALL(*fsimpl, readSymlink(_,_,_)).WillByDefault(defaultAction); EXPECT_CALL(*fsimpl, access(_,_)).WillRepeatedly(Return()); + + ON_CALL(*fsimpl, setContext(_)).WillByDefault(Invoke([this] (fspp::Context context) { + _context = std::move(context); + })); + ReturnIsDirOnLstat("/"); } -unique_ref FuseTest::TestFS() { - return make_unique_ref(fsimpl); +unique_ref FuseTest::TestFS(const std::vector& fuseOptions) { + return make_unique_ref(fsimpl, fuseOptions); } -FuseTest::TempTestFS::TempTestFS(shared_ptr fsimpl) +FuseTest::TempTestFS::TempTestFS(shared_ptr fsimpl, const std::vector& fuseOptions) :_mountDir(), _fuse([fsimpl] (Fuse*) {return fsimpl;}, []{}, "fusetest", boost::none), _fuse_thread(&_fuse) { - _fuse_thread.start(_mountDir.path(), {}); + _fuse_thread.start(_mountDir.path(), fuseOptions); } FuseTest::TempTestFS::~TempTestFS() { diff --git a/test/fspp/testutils/FuseTest.h b/test/fspp/testutils/FuseTest.h index b0261f51..c4075d0f 100644 --- a/test/fspp/testutils/FuseTest.h +++ b/test/fspp/testutils/FuseTest.h @@ -20,6 +20,7 @@ public: MockFilesystem(); virtual ~MockFilesystem(); + MOCK_METHOD(void, setContext, (fspp::Context&&), (override)); MOCK_METHOD(int, openFile, (const boost::filesystem::path&, int), (override)); MOCK_METHOD(void, closeFile, (int), (override)); MOCK_METHOD(void, lstat, (const boost::filesystem::path&, fspp::fuse::STAT*), (override)); @@ -54,7 +55,7 @@ public: class TempTestFS { public: - TempTestFS(std::shared_ptr fsimpl); + TempTestFS(std::shared_ptr fsimpl, const std::vector& fuseOptions = {}); virtual ~TempTestFS(); public: const boost::filesystem::path &mountDir() const; @@ -64,10 +65,18 @@ public: FuseThread _fuse_thread; }; - cpputils::unique_ref TestFS(); + cpputils::unique_ref TestFS(const std::vector& fuseOptions = {}); std::shared_ptr fsimpl; + const fspp::Context& context() const { + ASSERT(_context != boost::none, "Context wasn't correctly initialized"); + return *_context; + } +private: + boost::optional _context; + +public: //TODO Combine ReturnIsFile and ReturnIsFileFstat. This should be possible in gmock by either (a) using ::testing::Undefined as parameter type or (b) using action macros static ::testing::Action ReturnIsFile; diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index cee150f7..a4951eec 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -1,4 +1,2 @@ add_subdirectory(googletest) -add_subdirectory(spdlog) add_subdirectory(cryptopp) -add_subdirectory(range-v3) diff --git a/vendor/README b/vendor/README index d9111e8c..93bcf88d 100644 --- a/vendor/README +++ b/vendor/README @@ -1,7 +1,6 @@ This directory contains external projects, taken from the following locations: googletest: https://github.com/google/googletest/tree/release-1.10.0 -spdlog: https://github.com/gabime/spdlog/tree/v1.4.2/include/spdlog + - changed: https://github.com/google/googletest/pull/2514 cryptopp: https://github.com/weidai11/cryptopp/tree/CRYPTOPP_8_2_0 - changed: added CMakeLists.txt and cryptopp-config.cmake from https://github.com/noloader/cryptopp-cmake/tree/CRYPTOPP_8_2_0 - changed: commented out line including winapifamily.h in CMakeLists.txt -range-v3: https://github.com/ericniebler/range-v3/tree/0.9.1 diff --git a/vendor/googletest/gtest/googlemock/include/gmock/internal/gmock-pp.h b/vendor/googletest/gtest/googlemock/include/gmock/internal/gmock-pp.h index 1ab80e1c..a077f776 100644 --- a/vendor/googletest/gtest/googlemock/include/gmock/internal/gmock-pp.h +++ b/vendor/googletest/gtest/googlemock/include/gmock/internal/gmock-pp.h @@ -1,19 +1,6 @@ #ifndef THIRD_PARTY_GOOGLETEST_GOOGLEMOCK_INCLUDE_GMOCK_PP_H_ #define THIRD_PARTY_GOOGLETEST_GOOGLEMOCK_INCLUDE_GMOCK_PP_H_ -#undef GMOCK_PP_INTERNAL_USE_MSVC -#if defined(__clang__) -#define GMOCK_PP_INTERNAL_USE_MSVC 0 -#elif defined(_MSC_VER) -// TODO(iserna): Also verify tradional versus comformant preprocessor. -static_assert( - _MSC_VER >= 1900, - "MSVC version not supported. There is support for MSVC 14.0 and above."); -#define GMOCK_PP_INTERNAL_USE_MSVC 1 -#else -#define GMOCK_PP_INTERNAL_USE_MSVC 0 -#endif - // Expands and concatenates the arguments. Constructed macros reevaluate. #define GMOCK_PP_CAT(_1, _2) GMOCK_PP_INTERNAL_CAT(_1, _2) @@ -29,10 +16,6 @@ static_assert( // Returns the only argument. #define GMOCK_PP_IDENTITY(_1) _1 -// MSVC preprocessor collapses __VA_ARGS__ in a single argument, we use a -// CAT-like directive to force correct evaluation. Each macro has its own. -#if GMOCK_PP_INTERNAL_USE_MSVC - // Evaluates to the number of arguments after expansion. // // #define PAIR x, y @@ -43,45 +26,29 @@ static_assert( // GMOCK_PP_NARG(PAIR) => 2 // // Requires: the number of arguments after expansion is at most 15. -#define GMOCK_PP_NARG(...) \ - GMOCK_PP_INTERNAL_NARG_CAT( \ - GMOCK_PP_INTERNAL_INTERNAL_16TH(__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, \ - 8, 7, 6, 5, 4, 3, 2, 1), ) +#define GMOCK_PP_NARG(...) \ + GMOCK_PP_INTERNAL_16TH( \ + (__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)) // Returns 1 if the expansion of arguments has an unprotected comma. Otherwise // returns 0. Requires no more than 15 unprotected commas. -#define GMOCK_PP_HAS_COMMA(...) \ - GMOCK_PP_INTERNAL_HAS_COMMA_CAT( \ - GMOCK_PP_INTERNAL_INTERNAL_16TH(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ - 1, 1, 1, 1, 1, 0), ) +#define GMOCK_PP_HAS_COMMA(...) \ + GMOCK_PP_INTERNAL_16TH( \ + (__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)) + // Returns the first argument. #define GMOCK_PP_HEAD(...) \ - GMOCK_PP_INTERNAL_HEAD_CAT(GMOCK_PP_INTERNAL_HEAD(__VA_ARGS__), ) + GMOCK_PP_INTERNAL_HEAD((__VA_ARGS__)) // Returns the tail. A variadic list of all arguments minus the first. Requires // at least one argument. #define GMOCK_PP_TAIL(...) \ - GMOCK_PP_INTERNAL_TAIL_CAT(GMOCK_PP_INTERNAL_TAIL(__VA_ARGS__), ) + GMOCK_PP_INTERNAL_TAIL((__VA_ARGS__)) // Calls CAT(_Macro, NARG(__VA_ARGS__))(__VA_ARGS__) #define GMOCK_PP_VARIADIC_CALL(_Macro, ...) \ - GMOCK_PP_INTERNAL_VARIADIC_CALL_CAT( \ - GMOCK_PP_CAT(_Macro, GMOCK_PP_NARG(__VA_ARGS__))(__VA_ARGS__), ) - -#else // GMOCK_PP_INTERNAL_USE_MSVC - -#define GMOCK_PP_NARG(...) \ - GMOCK_PP_INTERNAL_INTERNAL_16TH(__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, \ - 7, 6, 5, 4, 3, 2, 1) -#define GMOCK_PP_HAS_COMMA(...) \ - GMOCK_PP_INTERNAL_INTERNAL_16TH(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ - 1, 1, 1, 1, 0) -#define GMOCK_PP_HEAD(...) GMOCK_PP_INTERNAL_HEAD(__VA_ARGS__) -#define GMOCK_PP_TAIL(...) GMOCK_PP_INTERNAL_TAIL(__VA_ARGS__) -#define GMOCK_PP_VARIADIC_CALL(_Macro, ...) \ - GMOCK_PP_CAT(_Macro, GMOCK_PP_NARG(__VA_ARGS__))(__VA_ARGS__) - -#endif // GMOCK_PP_INTERNAL_USE_MSVC + GMOCK_PP_IDENTITY( \ + GMOCK_PP_CAT(_Macro, GMOCK_PP_NARG(__VA_ARGS__))(__VA_ARGS__)) // If the arguments after expansion have no tokens, evaluates to `1`. Otherwise // evaluates to `0`. @@ -140,7 +107,7 @@ static_assert( // Expands to 1 if the first argument starts with something in parentheses, // otherwise to 0. #define GMOCK_PP_IS_BEGIN_PARENS(...) \ - GMOCK_PP_INTERNAL_ALTERNATE_HEAD( \ + GMOCK_PP_HEAD( \ GMOCK_PP_CAT(GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_, \ GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C __VA_ARGS__)) @@ -179,10 +146,6 @@ static_assert( #define GMOCK_PP_INTENRAL_EMPTY_TUPLE (, , , , , , , , , , , , , , , ) #define GMOCK_PP_INTERNAL_CAT(_1, _2) _1##_2 #define GMOCK_PP_INTERNAL_STRINGIZE(...) #__VA_ARGS__ -#define GMOCK_PP_INTERNAL_INTERNAL_16TH(_1, _2, _3, _4, _5, _6, _7, _8, _9, \ - _10, _11, _12, _13, _14, _15, _16, \ - ...) \ - _16 #define GMOCK_PP_INTERNAL_CAT_5(_1, _2, _3, _4, _5) _1##_2##_3##_4##_5 #define GMOCK_PP_INTERNAL_IS_EMPTY(_1, _2, _3, _4) \ GMOCK_PP_HAS_COMMA(GMOCK_PP_INTERNAL_CAT_5(GMOCK_PP_INTERNAL_IS_EMPTY_CASE_, \ @@ -190,30 +153,24 @@ static_assert( #define GMOCK_PP_INTERNAL_IS_EMPTY_CASE_0001 , #define GMOCK_PP_INTERNAL_IF_1(_Then, _Else) _Then #define GMOCK_PP_INTERNAL_IF_0(_Then, _Else) _Else -#define GMOCK_PP_INTERNAL_HEAD(_1, ...) _1 -#define GMOCK_PP_INTERNAL_TAIL(_1, ...) __VA_ARGS__ -#if GMOCK_PP_INTERNAL_USE_MSVC -#define GMOCK_PP_INTERNAL_NARG_CAT(_1, _2) GMOCK_PP_INTERNAL_NARG_CAT_I(_1, _2) -#define GMOCK_PP_INTERNAL_HEAD_CAT(_1, _2) GMOCK_PP_INTERNAL_HEAD_CAT_I(_1, _2) -#define GMOCK_PP_INTERNAL_HAS_COMMA_CAT(_1, _2) \ - GMOCK_PP_INTERNAL_HAS_COMMA_CAT_I(_1, _2) -#define GMOCK_PP_INTERNAL_TAIL_CAT(_1, _2) GMOCK_PP_INTERNAL_TAIL_CAT_I(_1, _2) -#define GMOCK_PP_INTERNAL_VARIADIC_CALL_CAT(_1, _2) \ - GMOCK_PP_INTERNAL_VARIADIC_CALL_CAT_I(_1, _2) -#define GMOCK_PP_INTERNAL_NARG_CAT_I(_1, _2) _1##_2 -#define GMOCK_PP_INTERNAL_HEAD_CAT_I(_1, _2) _1##_2 -#define GMOCK_PP_INTERNAL_HAS_COMMA_CAT_I(_1, _2) _1##_2 -#define GMOCK_PP_INTERNAL_TAIL_CAT_I(_1, _2) _1##_2 -#define GMOCK_PP_INTERNAL_VARIADIC_CALL_CAT_I(_1, _2) _1##_2 -#define GMOCK_PP_INTERNAL_ALTERNATE_HEAD(...) \ - GMOCK_PP_INTERNAL_ALTERNATE_HEAD_CAT(GMOCK_PP_HEAD(__VA_ARGS__), ) -#define GMOCK_PP_INTERNAL_ALTERNATE_HEAD_CAT(_1, _2) \ - GMOCK_PP_INTERNAL_ALTERNATE_HEAD_CAT_I(_1, _2) -#define GMOCK_PP_INTERNAL_ALTERNATE_HEAD_CAT_I(_1, _2) _1##_2 -#else // GMOCK_PP_INTERNAL_USE_MSVC -#define GMOCK_PP_INTERNAL_ALTERNATE_HEAD(...) GMOCK_PP_HEAD(__VA_ARGS__) -#endif // GMOCK_PP_INTERNAL_USE_MSVC +// Because of MSVC treating a token with a comma in it as a single token when passed +// to another macro, we need to force it to evaluate it as multiple tokens. We do that +// by using a "IDENTITY(MACRO PARENTHESIZED_ARGS)" macro. +// We define one per possible macro that relies on this behavior. +// Note "_Args" must be parenthesized. +#define GMOCK_PP_INTERNAL_INTERNAL_16TH(_1, _2, _3, _4, _5, _6, _7, _8, _9, \ + _10, _11, _12, _13, _14, _15, _16, \ + ...) \ + _16 +#define GMOCK_PP_INTERNAL_16TH(_Args) \ + GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_16TH _Args) +#define GMOCK_PP_INTERNAL_INTERNAL_HEAD(_1, ...) _1 +#define GMOCK_PP_INTERNAL_HEAD(_Args) \ + GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_HEAD _Args) +#define GMOCK_PP_INTERNAL_INTERNAL_TAIL(_1, ...) __VA_ARGS__ +#define GMOCK_PP_INTERNAL_TAIL(_Args) \ + GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_TAIL _Args) #define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C(...) 1 _ #define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_1 1, diff --git a/vendor/googletest/gtest/googlemock/test/gmock-pp_test.cc b/vendor/googletest/gtest/googlemock/test/gmock-pp_test.cc index 7387d398..96b7b26c 100644 --- a/vendor/googletest/gtest/googlemock/test/gmock-pp_test.cc +++ b/vendor/googletest/gtest/googlemock/test/gmock-pp_test.cc @@ -1,5 +1,10 @@ #include "gmock/internal/gmock-pp.h" +// Used to test MSVC treating __VA_ARGS__ with a comma in it as one value +#define GMOCK_TEST_REPLACE_comma_WITH_COMMA_I_comma , +#define GMOCK_TEST_REPLACE_comma_WITH_COMMA(x) \ + GMOCK_PP_CAT(GMOCK_TEST_REPLACE_comma_WITH_COMMA_I_, x) + // Static assertions. namespace testing { namespace internal { @@ -17,6 +22,11 @@ static_assert(GMOCK_PP_NARG(x, y, z, w) == 4, ""); static_assert(!GMOCK_PP_HAS_COMMA(), ""); static_assert(GMOCK_PP_HAS_COMMA(b, ), ""); static_assert(!GMOCK_PP_HAS_COMMA((, )), ""); +static_assert( + GMOCK_PP_HAS_COMMA(GMOCK_TEST_REPLACE_comma_WITH_COMMA(comma)), ""); +static_assert( + GMOCK_PP_HAS_COMMA(GMOCK_TEST_REPLACE_comma_WITH_COMMA(comma(unrelated))), + ""); static_assert(!GMOCK_PP_IS_EMPTY(, ), ""); static_assert(!GMOCK_PP_IS_EMPTY(a), ""); static_assert(!GMOCK_PP_IS_EMPTY(()), ""); diff --git a/vendor/range-v3/CMakeLists.txt b/vendor/range-v3/CMakeLists.txt deleted file mode 100644 index 4c2eda73..00000000 --- a/vendor/range-v3/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_library(range-v3 INTERFACE) -target_include_directories(range-v3 SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/range-v3/include) diff --git a/vendor/range-v3/range-v3/.buckconfig b/vendor/range-v3/range-v3/.buckconfig deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/range-v3/range-v3/.clang-format b/vendor/range-v3/range-v3/.clang-format deleted file mode 100644 index 39e33cd1..00000000 --- a/vendor/range-v3/range-v3/.clang-format +++ /dev/null @@ -1,92 +0,0 @@ -{ - AccessModifierOffset: -4, - AlignAfterOpenBracket: Align, - AlignEscapedNewlinesLeft: true, - AlignTrailingComments: true, - AllowAllParametersOfDeclarationOnNextLine: false, - AllowShortBlocksOnASingleLine: true, - AllowShortCaseLabelsOnASingleLine: false, - AllowShortFunctionsOnASingleLine: None, - AllowShortIfStatementsOnASingleLine: false, - AllowShortLoopsOnASingleLine: false, - AlwaysBreakBeforeMultilineStrings: true, - AlwaysBreakAfterReturnType: None, - AlwaysBreakTemplateDeclarations: true, - BinPackArguments: false, - BinPackParameters: true, - BraceWrapping: { - AfterCaseLabel: true, - AfterClass: true, - AfterControlStatement: true, - AfterEnum: true, - AfterFunction: true, - AfterNamespace: true, - AfterStruct: true, - AfterUnion: true, - AfterExternBlock: true, - BeforeCatch: true, - BeforeElse: true, - IndentBraces: false, - SplitEmptyFunction: false, - SplitEmptyRecord: false, - SplitEmptyNamespace: true, - }, - BreakBeforeBinaryOperators: false, - BreakBeforeBraces: Custom, - BreakBeforeTernaryOperators: true, - BreakConstructorInitializers: BeforeComma, - BreakInheritanceList: BeforeComma, - ColumnLimit: 90, - ConstructorInitializerAllOnOneLineOrOnePerLine: false, - ConstructorInitializerIndentWidth: 2, - ContinuationIndentWidth: 4, - Cpp11BracedListStyle: true, - DerivePointerAlignment: false, - ExperimentalAutoDetectBinPacking: false, - ForEachMacros: ['RANGES_FOR',], - IncludeBlocks: Regroup, - IncludeCategories: [ - { Regex: '^$', - Priority: 2}, - { Regex: '^<.*>$', - Priority: 1}, - ], - IndentCaseLabels: false, - IndentFunctionDeclarationAfterType: false, - IndentWidth: 4, - KeepEmptyLinesAtTheStartOfBlocks: true, - Language: Cpp, - MaxEmptyLinesToKeep: 1, - MacroBlockBegin: "^(RANGES|META)_BEGIN_(NAMESPACE_(STD|VERSION|CONTAINER)|NIEBLOID)$", - MacroBlockEnd: "^(RANGES|META)_END_(NAMESPACE_(STD|VERSION|CONTAINER)|NIEBLOID)$", - NamespaceIndentation: All, - PenaltyBreakBeforeFirstCallParameter: 10, - PenaltyReturnTypeOnItsOwnLine: 1000, - PointerAlignment: Middle, - SpaceAfterControlStatementKeyword: false, - SpaceAfterTemplateKeyword: false, - SpaceBeforeAssignmentOperators: true, - SpaceBeforeParens: Never, - SpaceInEmptyParentheses: false, - SpacesBeforeTrailingComments: 1, - SpacesInAngles: false, - SpacesInCStyleCastParentheses: false, - SpacesInParentheses: false, - Standard: Cpp11, - StatementMacros: [ - 'RANGES_INLINE_VARIABLE', - 'RANGES_DEFINE_CPO', - 'CPP_member', - 'CPP_broken_friend_member', - ], - TabWidth: 4, - UseTab: Never, -} diff --git a/vendor/range-v3/range-v3/.gitignore b/vendor/range-v3/range-v3/.gitignore deleted file mode 100644 index 4f87ac16..00000000 --- a/vendor/range-v3/range-v3/.gitignore +++ /dev/null @@ -1,237 +0,0 @@ -## Copyright (c) 2013 GitHub, Inc. -## -## 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. - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# Clion files -.idea - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -x64/ -build*/ -bld/ -[Bb]in/ -[Oo]bj/ -bazel-* -cmake-build-*/ - -# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets -!packages/*/build/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -#NUNIT -*.VisualState.xml -TestResult.xml - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding addin-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -*.ncrunch* -_NCrunch_* -.*crunch*.local.xml - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.Publish.xml -*.azurePubxml - -# NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line -#packages/ -## TODO: If the tool you use requires repositories.config, also uncomment the next line -#!packages/repositories.config - -# Windows Azure Build Output -csx/ -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Buck build artefacts -.buckd/ -buck-out/ - -# Others -sql/ -*.Cache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.[Pp]ublish.xml -*.pfx -*.publishsettings - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -App_Data/*.mdf -App_Data/*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# ========================= -# Windows detritus -# ========================= - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Visual Studio stuff -*.VC.db -*.VC.opendb -.vscode/ -.vs/ - -\#*# -.#* diff --git a/vendor/range-v3/range-v3/.travis.yml b/vendor/range-v3/range-v3/.travis.yml deleted file mode 100644 index 6613a4dd..00000000 --- a/vendor/range-v3/range-v3/.travis.yml +++ /dev/null @@ -1,303 +0,0 @@ -# Copyright Louis Dionne 2013-2016 -# Copyright Gonzalo BG 2014-2017 -# Copyright Julian Becker 2015 -# Copyright Manu Sánchez 2015 -# Copyright Casey Carter 2015-2017 -# Copyright Eric Niebler 2015-2016 -# Copyright Paul Fultz II 2015-2016 -# Copyright Jakub Szuppe 2016. - -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE.txt or copy at http://boost.org/LICENSE_1_0.txt) - -# Adapted from various sources, including: -# - Louis Dionne's Hana: https://github.com/ldionne/hana -# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit -language: cpp -dist: xenial -script: cmake - -git: - depth: 1 - -env: - global: - - DEPS_DIR=${TRAVIS_BUILD_DIR}/deps - - CMAKE_VERSION="3.12.0" - -cache: - directories: - - ${DEPS_DIR}/cmake-${CMAKE_VERSION} - -matrix: - include: - - env: BUILD_TYPE=Release CPP=1z SYSTEM_LIBCXX=On - os: osx - compiler: clang - - # The ASAN build in install_libcxx.sh doesn't work for versions < 4 - - # clang 3.6 C++17/14 Release libc++ - - env: CLANG_VERSION=3.6 BUILD_TYPE=Release CPP=14 LIBCXX=On HEADERS=On - os: linux - addons: &clang36 - apt: - packages: - - clang-3.6 - - libstdc++-5-dev - - valgrind - sources: - - ubuntu-toolchain-r-test - - # clang 3.7 C++17/14 Release libc++ - - env: CLANG_VERSION=3.7 BUILD_TYPE=Release CPP=14 LIBCXX=On HEADERS=On - os: linux - addons: &clang37 - apt: - packages: - - clang-3.7 - - libstdc++-5-dev - - valgrind - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-3.7 - - sourceline: 'deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.7 main' - key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' - - # clang 3.8 C++17/14 Release libc++ - - env: CLANG_VERSION=3.8 BUILD_TYPE=Release CPP=1z LIBCXX=On - os: linux - addons: &clang38 - apt: - packages: - - util-linux - - clang-3.8 - - libstdc++-5-dev - - valgrind - sources: - - ubuntu-toolchain-r-test - - - env: CLANG_VERSION=3.8 BUILD_TYPE=Release CPP=14 LIBCXX=On HEADERS=On - os: linux - addons: *clang38 - - # clang 3.9 C++17/14 Release libc++ - - env: CLANG_VERSION=3.9 BUILD_TYPE=Release CPP=1z LIBCXX=On - os: linux - addons: &clang39 - apt: - packages: - - util-linux - - clang-3.9 - - libstdc++-6-dev - - valgrind - sources: - - ubuntu-toolchain-r-test - - - env: CLANG_VERSION=3.9 BUILD_TYPE=Release CPP=14 LIBCXX=On HEADERS=On - os: linux - addons: *clang39 - - # clang 5 C++17/14/1z Debug/Release-ASAN libc++, 17 Debug libstdc++ - - env: CLANG_VERSION=5.0 BUILD_TYPE=Debug CPP=1z LIBCXX=On - os: linux - addons: &clang5 - apt: - packages: - - clang-5.0 - - libstdc++-6-dev - - valgrind - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-5.0 - - sourceline: 'deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main' - key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' - - - env: CLANG_VERSION=5.0 BUILD_TYPE=Release CPP=1z ASAN=On LIBCXX=On HEADERS=On - os: linux - addons: *clang5 - - - env: CLANG_VERSION=5.0 BUILD_TYPE=Debug CPP=14 LIBCXX=On - os: linux - addons: *clang5 - - - env: CLANG_VERSION=5.0 BUILD_TYPE=Release CPP=14 ASAN=On LIBCXX=On - os: linux - addons: *clang5 - - - env: CLANG_VERSION=5.0 BUILD_TYPE=Release CPP=1z - os: linux - addons: *clang5 - - # Module build is on the floor - # - env: CLANG_VERSION=5.0 BUILD_TYPE=Release CPP=1z MSAN=On LIBCXX=On CLANG_MODULES=On - # os: linux - # addons: *clang5 - - # gcc-5 C++17/C++14 Release - - env: GCC_VERSION=5 BUILD_TYPE=Release CPP=1z - os: linux - addons: &gcc5 - apt: - packages: - - g++-5 - - valgrind - sources: - - ubuntu-toolchain-r-test - - - env: GCC_VERSION=5 BUILD_TYPE=Release CPP=14 HEADERS=On - os: linux - addons: *gcc5 - - # gcc-6 C++17/14/1z Debug/Release - - env: GCC_VERSION=6 BUILD_TYPE=Debug CPP=1z - os: linux - addons: &gcc6 - apt: - packages: - - g++-6 - - valgrind - sources: - - ubuntu-toolchain-r-test - - - env: GCC_VERSION=6 BUILD_TYPE=Release CPP=1z - os: linux - addons: *gcc6 - - - env: GCC_VERSION=6 BUILD_TYPE=Debug CPP=14 HEADERS=On - os: linux - addons: *gcc6 - - - env: GCC_VERSION=6 BUILD_TYPE=Release CPP=14 - os: linux - addons: *gcc6 - - # gcc-7 C++17/14/1z Debug/Release - - env: GCC_VERSION=7 BUILD_TYPE=Debug CPP=1z - os: linux - addons: &gcc7 - apt: - packages: - - g++-7 - - valgrind - sources: - - ubuntu-toolchain-r-test - - - env: GCC_VERSION=7 BUILD_TYPE=Release CPP=1z - os: linux - addons: *gcc7 - - - env: GCC_VERSION=7 BUILD_TYPE=Debug CPP=14 HEADERS=On - os: linux - addons: *gcc7 - - - env: GCC_VERSION=7 BUILD_TYPE=Release CPP=14 - os: linux - addons: *gcc7 - - - env: GCC_VERSION=7 BUILD_TYPE=Release CPP=1z CONCEPTS=On - os: linux - addons: *gcc7 - - # gcc-8 C++17/14/1z Debug/Release - - env: GCC_VERSION=8 BUILD_TYPE=Debug CPP=1z - os: linux - addons: &gcc8 - apt: - packages: - - g++-8 - - valgrind - sources: - - ubuntu-toolchain-r-test - - - env: GCC_VERSION=8 BUILD_TYPE=Release CPP=1z - os: linux - addons: *gcc8 - - - env: GCC_VERSION=8 BUILD_TYPE=Debug CPP=14 HEADERS=On - os: linux - addons: *gcc8 - - - env: GCC_VERSION=8 BUILD_TYPE=Release CPP=14 - os: linux - addons: *gcc8 - - - env: GCC_VERSION=8 BUILD_TYPE=Release CPP=1z CONCEPTS=On - os: linux - addons: *gcc8 - -# Install dependencies -before_install: - - set -e - - | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then - brew update - brew install gnu-sed - brew install gnu-which - brew upgrade cmake - export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH" - elif [ "$BUILD_TYPE" == "Release" -a "$ASAN" != "On" -a "$MSAN" != "On" ]; then - USE_VALGRIND=On - fi - - | - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then - if [ -f ${DEPS_DIR}/cmake-${CMAKE_VERSION}/cached ]; then - echo "Using cached cmake version ${CMAKE_VERSION}." - else - CMAKE_URL="https://cmake.org/files/v3.12/cmake-${CMAKE_VERSION}-Linux-x86_64.tar.gz" - mkdir -p ${DEPS_DIR}/cmake-${CMAKE_VERSION} - travis_retry wget --no-check-certificate --quiet -O - "${CMAKE_URL}" | tar --strip-components=1 -xz -C ${DEPS_DIR}/cmake-${CMAKE_VERSION} - touch ${DEPS_DIR}/cmake-${CMAKE_VERSION}/cached - fi - export PATH="${DEPS_DIR}/cmake-${CMAKE_VERSION}/bin:${PATH}" - fi - - if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi - - if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi - - which $CXX && $CXX --version - - which $CC - - if [ "$USE_VALGRIND" == "On" ]; then which valgrind; fi - - if [ "$ASAN" == "On" ]; then export SANITIZER="Address;Undefined"; fi - - if [ "$MSAN" == "On" ]; then export SANITIZER="MemoryWithOrigins"; fi - - if [ -n "$CLANG_VERSION" ]; then PATH="${PATH}" CXX="$CXX" CC="$CC" ./install_libcxx.sh; fi - -install: - # Workaround for valgrind bug: https://bugs.kde.org/show_bug.cgi?id=326469. - # It is fixed in valgrind 3.10 so this won't be necessary if someone - # replaces the current valgrind (3.7) with valgrind-3.10 - - | - if [ "$USE_VALGRIND" == "On" ]; then - sed -i 's/march=native/msse4.2/' cmake/ranges_flags.cmake - # We need to explicitly initialize std::random_device on libstdc++ to avoid using RDRAND - # since valgrind doesn't understand the instruction. - CXX_FLAGS="${CXX_FLAGS} -DRANGES_WORKAROUND_VALGRIND_RDRAND" - fi - - if [ "$GCC_VERSION" == "5" ]; then CXX_FLAGS="${CXX_FLAGS} -DRANGES_CXX_CONSTEXPR=RANGES_CXX_CONSTEXPR11"; fi - - | - if [ "$LIBCXX" == "On" ]; then - CXX_FLAGS="${CXX_FLAGS} -stdlib=libc++ -nostdinc++ -cxx-isystem ${TRAVIS_BUILD_DIR}/llvm/include/c++/v1/ -Wno-unused-command-line-argument" - CXX_LINKER_FLAGS="${CXX_LINKER_FLAGS} -L ${TRAVIS_BUILD_DIR}/llvm/lib -Wl,-rpath,${TRAVIS_BUILD_DIR}/llvm/lib -lc++abi" - if [ -n "$CLANG_VERSION" ]; then - if [ "$ASAN" == "On" ]; then - CXX_FLAGS="${CXX_FLAGS} -fsanitize=address" - elif [ "$MSAN" == "On" ]; then - CXX_FLAGS="${CXX_FLAGS} -fsanitize=memory" - fi - fi - fi - - - mkdir -p build - # This cd works, but causes the shell to exit on OSX with set -e. I don't even. - - set +e; cd build; set -e; pwd - - cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" -DCMAKE_EXE_LINKER_FLAGS="${CXX_LINKER_FLAGS}" -DRANGES_CXX_STD=$CPP -DRANGE_V3_HEADER_CHECKS=$HEADERS -DRANGES_PREFER_REAL_CONCEPTS=$CONCEPTS -DRANGES_VERBOSE_BUILD=On -DRANGES_ASAN=$ASAN -DRANGES_MSAN=$MSAN -DRANGES_DEEP_STL_INTEGRATION=On -Wdev - - cat CMakeFiles/CMakeError.log || true - - cat CMakeFiles/CMakeOutput.log || true - - if [ "$CLANG_MODULES" == "On" -a "$LIBCXX" == "On" ]; then cmake .. -DRANGES_MODULES=On -DRANGES_LIBCXX_MODULE="${TRAVIS_BUILD_DIR}/llvm/include/c++/v1/module.modulemap"; fi - - - make -j2 VERBOSE=1 - -script: - - if [ "$USE_VALGRIND" == "On" ]; then CTEST_FLAGS="-D ExperimentalMemCheck"; fi - - ctest -j2 -VV ${CTEST_FLAGS} - -notifications: - email: false diff --git a/vendor/range-v3/range-v3/BUCK b/vendor/range-v3/range-v3/BUCK deleted file mode 100644 index 4b726ddb..00000000 --- a/vendor/range-v3/range-v3/BUCK +++ /dev/null @@ -1,42 +0,0 @@ -prebuilt_cxx_library( - name = 'concepts', - header_namespace = 'concepts', - header_only = True, - exported_headers = subdir_glob([ - ('include/concepts', '**/*.hpp'), - ]), - licenses = [ - 'LICENSE.txt', - ], -) - -prebuilt_cxx_library( - name = 'meta', - header_namespace = 'meta', - header_only = True, - exported_headers = subdir_glob([ - ('include/meta', '**/*.hpp'), - ]), - licenses = [ - 'LICENSE.txt', - ], -) - -prebuilt_cxx_library( - name = 'range-v3', - header_namespace = 'range/v3', - header_only = True, - exported_headers = subdir_glob([ - ('include/range/v3', '**/*.hpp'), - ]), - licenses = [ - 'LICENSE.txt', - ], - visibility = [ - 'PUBLIC' - ], - deps = [ - ':concepts', - ':meta', - ], -) diff --git a/vendor/range-v3/range-v3/BUILD.bazel b/vendor/range-v3/range-v3/BUILD.bazel deleted file mode 100644 index 9b1a8a47..00000000 --- a/vendor/range-v3/range-v3/BUILD.bazel +++ /dev/null @@ -1,42 +0,0 @@ -cc_library( - name = 'concepts', - hdrs = glob([ - 'include/concepts/**/*.hpp', - ]), - includes = [ - "include", - ], -) - -cc_library( - name = 'meta', - hdrs = glob([ - 'include/meta/**/*.hpp', - ]), - includes = [ - "include", - ], -) - -cc_library( - name = 'std', - hdrs = glob([ - 'include/std/**/*.hpp', - ]), - includes = [ - "include", - ], -) - -cc_library( - name = 'range-v3', - hdrs = glob([ - 'include/range/v3/**/*.hpp', - ]), - visibility = ["//visibility:public"], - deps = [ - ':concepts', - ':meta', - ':std', - ], -) diff --git a/vendor/range-v3/range-v3/CMakeLists.txt b/vendor/range-v3/range-v3/CMakeLists.txt deleted file mode 100644 index 7e9b3f46..00000000 --- a/vendor/range-v3/range-v3/CMakeLists.txt +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright Eric Niebler 2014 -# Copyright Gonzalo Brito Gadeschi 2014, 2017 -# Copyright Louis Dionne 2015 -# Copyright Casey Carter 2016 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) - -cmake_minimum_required(VERSION 3.6) -get_directory_property(is_subproject PARENT_DIRECTORY) - -if(NOT is_subproject) - set(is_standalone YES) -else() - set(is_standalone NO) -endif() - -project(Range-v3 CXX) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export compilation data-base -set_property(GLOBAL PROPERTY USE_FOLDERS ON) - -add_library(meta INTERFACE) -target_include_directories(meta INTERFACE $) -target_include_directories(meta SYSTEM INTERFACE $/include>) -target_compile_options(meta INTERFACE $<$:/permissive->) - -add_library(concepts INTERFACE) -target_include_directories(concepts INTERFACE $) -target_include_directories(concepts SYSTEM INTERFACE $/include>) -target_compile_options(concepts INTERFACE $<$:/permissive- /experimental:preprocessor /wd5105>) -target_link_libraries(concepts INTERFACE meta) - -add_library(range-v3 INTERFACE) -target_include_directories(range-v3 INTERFACE $) -target_include_directories(range-v3 SYSTEM INTERFACE $/include>) -target_compile_options(range-v3 INTERFACE $<$:/permissive->) -target_link_libraries(range-v3 INTERFACE concepts meta) - -function(rv3_add_test TESTNAME EXENAME FIRSTSOURCE) - add_executable(${EXENAME} ${FIRSTSOURCE} ${ARGN}) - target_link_libraries(${EXENAME} range-v3) - add_test(${TESTNAME} ${EXENAME}) -endfunction(rv3_add_test) - -include(ranges_options) -include(ranges_env) -include(ranges_flags) - -if(RANGE_V3_DOCS) - add_subdirectory(doc) -endif() - -if(RANGE_V3_TESTS) - include(CTest) - add_subdirectory(test) -endif() - -if(RANGE_V3_EXAMPLES) - add_subdirectory(example) -endif() - -if(RANGE_V3_PERF) - add_subdirectory(perf) -endif() - -# Add header files as sources to fix MSVS 2017 not finding source during debugging -file(GLOB_RECURSE RANGE_V3_PUBLIC_HEADERS_ABSOLUTE - "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp") -add_custom_target(headers SOURCES ${RANGE_V3_PUBLIC_HEADERS_ABSOLUTE}) -set_target_properties(headers PROPERTIES FOLDER "header") - -# Test all headers -if(RANGE_V3_HEADER_CHECKS) - include(TestHeaders) - - file(GLOB_RECURSE RANGE_V3_PUBLIC_HEADERS - RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp") - # This header is not meant to be included directly: - list(REMOVE_ITEM RANGE_V3_PUBLIC_HEADERS std/detail/associated_types.hpp) - # Deprecated headers - if(CMAKE_CXX_COMPILER_ID STREQUAL GNU) - foreach(header ${RANGE_V3_PUBLIC_HEADERS}) - file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/${header}" is_deprecated - LIMIT_COUNT 1 - REGEX ".*RANGES_DEPRECATED_HEADER.*") - if(is_deprecated) - list(APPEND RANGE_V3_DEPRECATED_PUBLIC_HEADERS "${header}") - endif() - endforeach() - endif() - - add_header_test(test.range.v3.headers - EXCLUDE ${RANGE_V3_DEPRECATED_PUBLIC_HEADERS} - HEADERS ${RANGE_V3_PUBLIC_HEADERS}) - target_link_libraries(test.range.v3.headers PRIVATE range-v3) -endif() - -# Grab the range-v3 version numbers: -include(${CMAKE_CURRENT_SOURCE_DIR}/Version.cmake) -set(RANGE_V3_VERSION ${RANGE_V3_MAJOR}.${RANGE_V3_MINOR}.${RANGE_V3_PATCHLEVEL}) - -# Try to build a new version.hpp -configure_file(version.hpp.in include/range/v3/version.hpp @ONLY) -file(STRINGS ${CMAKE_CURRENT_BINARY_DIR}/include/range/v3/version.hpp RANGE_V3_OLD_VERSION_HPP) -file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/include/range/v3/version.hpp RANGE_V3_NEW_VERSION_HPP) - -# If the new version.hpp is materially different from the one in the source -# directory, update it, commit, and tag. -if(NOT RANGE_V3_NEW_VERSION_HPP STREQUAL RANGE_V3_OLD_VERSION_HPP) - # Check that README.md and Version.cmake are the only changed file: - execute_process( - COMMAND ${GIT_EXECUTABLE} -C "${CMAKE_CURRENT_SOURCE_DIR}" status --porcelain -uno - OUTPUT_VARIABLE RANGE_V3_GIT_STATUS - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - string(REPLACE "\n" ";" RANGE_V3_GIT_STATUS ${RANGE_V3_GIT_STATUS}) - if (NOT "x${RANGE_V3_GIT_STATUS}" STREQUAL "x M Version.cmake; M doc/release_notes.md") - message(FATAL_ERROR "Cannot update version.hpp: range-v3 source directory has a dirty status") - endif() - file( - COPY ${CMAKE_CURRENT_BINARY_DIR}/include/range/v3/version.hpp - DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/include/range/v3 - ) - execute_process( - COMMAND ${GIT_EXECUTABLE} -C "${CMAKE_CURRENT_SOURCE_DIR}" add -u - ) - execute_process( - COMMAND ${GIT_EXECUTABLE} -C "${CMAKE_CURRENT_SOURCE_DIR}" commit -m "${RANGE_V3_VERSION}" - ) - execute_process( - COMMAND ${GIT_EXECUTABLE} -C "${CMAKE_CURRENT_SOURCE_DIR}" tag -f -a "${RANGE_V3_VERSION}" -m "${RANGE_V3_VERSION}" - ) - find_program(CONAN_EXECUTABLE NAMES conan conan.exe) - if (NOT "x${CONAN_EXECUTABLE}" STREQUAL "xCONAN_EXECUTABLE-NOTFOUND") - message(STATUS "Exporting conanfile for new version") - execute_process( - COMMAND ${CONAN_EXECUTABLE} create . range-v3/${RANGE_V3_VERSION}@ericniebler/stable - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - ) - endif() - message(STATUS "Version updated to ${RANGE_V3_VERSION}. Don't forget to:") - message(STATUS " git push origin ") - message(STATUS "and (after that is merged to master) then:") - message(STATUS " conan create ${CMAKE_CURRENT_SOURCE_DIR} range-v3/${RANGE_V3_VERSION}@ericniebler/stable") - message(STATUS " conan upload --all range-v3/${RANGE_V3_VERSION}@ericniebler/stable") -endif() - -include(CMakePackageConfigHelpers) - -# write_basic_package_version_file(...) gained ARCH_INDEPENDENT in CMake 3.14. -# For CMake 3.6, this workaround makes the version file ARCH_INDEPENDENT -# by making CMAKE_SIZEOF_VOID_P empty. -set(OLD_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) -set(CMAKE_SIZEOF_VOID_P "") -write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/range-v3-config-version.cmake - VERSION ${RANGE_V3_VERSION} - COMPATIBILITY ExactVersion -) -set(CMAKE_SIZEOF_VOID_P ${OLD_CMAKE_SIZEOF_VOID_P}) - -install(TARGETS concepts meta range-v3 EXPORT range-v3-targets DESTINATION lib) -install(EXPORT range-v3-targets FILE range-v3-config.cmake DESTINATION lib/cmake/range-v3) -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/range-v3-config-version.cmake - DESTINATION lib/cmake/range-v3) -install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN "*") - -export(EXPORT range-v3-targets FILE range-v3-config.cmake) diff --git a/vendor/range-v3/range-v3/CREDITS.md b/vendor/range-v3/range-v3/CREDITS.md deleted file mode 100644 index 46bd9798..00000000 --- a/vendor/range-v3/range-v3/CREDITS.md +++ /dev/null @@ -1,48 +0,0 @@ -Acknowledgements ----------------- - -In range-v3, I have integrated many ideas that come from other people. I would be remiss to not mention them. Many others helped either directly or indirectly in a variety of ways. In no particular order... - -| Contributor | Contribution | -|-----------------------------|------| -| Jeremy Siek | Container algorithms (in Boost pre-history), Boost.Iterators | -| Thorston Ottoson | Boost.Range v1 | -| Neil Groves | Boost.Range v2 | -| David Abrahams, Thomas Witt | Boost.Iterators, Sentinels | -| Sean Parent | ASL, Projections, View / Range distinction, much Generic Program wisdom besides | -| Dietmar Kühl | Array Traits, Property Map | -| Andrew Sutton | C++ Concepts "Lite", Origin Libraries, Palo Alto Report | -| Doug Gregor | C++0x Concepts | -| Casey Carter | Co-author and Editor, Ranges TS; major code contributions | -| Gonzalo Brito Gadeschi | Many ideas, bug reports, and code contributions | -| Alexander Stepanov | STL, Generic Programming, Iterators, Elements of Programming, etc. | -| Bjarne Stroustrup | A tireless effort to add proper support for Generic Programming to C++, early support for my Ranges proposal | -| Herb Sutter | Early support for my Ranges proposal | -| The Standard C++ Foundation | A generous grant supporting my Ranges work | - -An Abreviated History --------------------- - -**Range v1** - -I came to Boost in the early 2000's. By that time, Boost already had a Range library (Thorston Ottoson's). At this time, Boost.Range was little more that the `begin` and `end` free functions, and range-based overloads of the STL algorithms that dispatched to the iterator-based overloads in namespace `std`. - -Boost also already had the Iterators library by David Abrahams and Jeremy Siek. This library had iterator adaptors like `filter_iterator` and `transform_iterator`. - -**Range v2** - -It seemed natural to me that the Range library and the adaptors from the Iterators library should be combined. I wrote the `filter` and `transform` range adaptors, commandeered the pipe operator (`|`) from bash for chaining, and put a rough library together called Range_ex in the Boost File Vault (which would later become the Boost Sandbox). I saw problems with my design and never finished it. - -A few years later, Neil Groves picked up some of the ideas in my Range\_ex, polished them a great deal, published his own Range\_ex library, and submitted it to Boost. It became Boost.Range v2. At the time of writing (March, 2017), it is the version still shipping with Boost. - -**Range v3** - -In 2013, I published a blog post called ["Out Parameters, Move Semantics, and Stateful Algorithms"](http://ericniebler.com/2013/10/13/out-parameters-vs-move-semantics/) that turned my mind to ranges once again. Following that, it became clear to me that the Boost.Range library, designed for C++98, needed a facelift for the post-C++11 world. I began what I believed at the time would be a quick hack to bring Boost.Range into the modern world. I called it "Range v3", thinking it would become the third major version of the Boost.Range library. Subsequent posts detailed the evolution of my thinking as range-v3 took shape. - -**Standardization** - -Around this time, some big thinkers in the C++ community were working to resurrect the effort to add Concepts to C++. They published a paper ([N3351](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3351.pdf)) that would become known as the **"Palo Alto Report"** which detailed the necessary and sufficient language and library support for a concept-checked version of the Standard Template Library. The authors of the paper included Alexander Stepanov, Bjarne Stroustrup, Sean Parent, Andrew Sutton, and more. Andrew Sutton began working in earnest to realize the core language changes, an effort that became known as "Concepts Lite". (It is now the Concepts TS.) - -I decided early on that Concepts Lite, or something like it, would become part of Standard C++. Recognizing that C++ would need a concept-ified Standard Library to go with the language feature, I began evolving range-v3 in that direction, eventually submitting ["Ranges for the Standard Library, Revision 1"](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4128.html) to the C++ Standardization Committee, together with Andrew Sutton and Sean Parent. The Committee approved the direction in late 2014, and so it goes... - -Today (2017-03), we are very close to a final Ranges TS and are on target to integrate into Standard C++ in 2020, with *much* more to come. Stay tuned. diff --git a/vendor/range-v3/range-v3/LICENSE.txt b/vendor/range-v3/range-v3/LICENSE.txt deleted file mode 100644 index 698193e9..00000000 --- a/vendor/range-v3/range-v3/LICENSE.txt +++ /dev/null @@ -1,151 +0,0 @@ -======================================================== -Boost Software License - Version 1.0 - August 17th, 2003 -======================================================== - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -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, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -============================================================================== -libc++ License -============================================================================== - -The libc++ library is dual licensed under both the University of Illinois -"BSD-Like" license and the MIT license. As a user of this code you may choose -to use it under either license. As a contributor, you agree to allow your code -to be used under both. - -Full text of the relevant licenses is included below. - -============================================================================== - -University of Illinois/NCSA -Open Source License - -Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT -http://llvm.org/svn/llvm-project/libcxx/trunk/CREDITS.TXT - -All rights reserved. - -Developed by: - - LLVM Team - - University of Illinois at Urbana-Champaign - - http://llvm.org - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal with -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: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimers. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimers in the - documentation and/or other materials provided with the distribution. - - * Neither the names of the LLVM Team, University of Illinois at - Urbana-Champaign, nor the names of its contributors may be used to - endorse or promote products derived from this Software without specific - prior written permission. - -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 -CONTRIBUTORS 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 WITH THE -SOFTWARE. - -============================================================================== - -Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT - http://llvm.org/svn/llvm-project/libcxx/trunk/CREDITS.TXT - -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. - -============================================================================== -Stepanov and McJones, "Elements of Programming" license -============================================================================== - -// Copyright (c) 2009 Alexander Stepanov and Paul McJones -// -// Permission to use, copy, modify, distribute and sell this software -// and its documentation for any purpose is hereby granted without -// fee, provided that the above copyright notice appear in all copies -// and that both that copyright notice and this permission notice -// appear in supporting documentation. The authors make no -// representations about the suitability of this software for any -// purpose. It is provided "as is" without express or implied -// warranty. -// -// Algorithms from -// Elements of Programming -// by Alexander Stepanov and Paul McJones -// Addison-Wesley Professional, 2009 - -============================================================================== -SGI C++ Standard Template Library license -============================================================================== - -// Copyright (c) 1994 -// Hewlett-Packard Company -// -// Permission to use, copy, modify, distribute and sell this software -// and its documentation for any purpose is hereby granted without fee, -// provided that the above copyright notice appear in all copies and -// that both that copyright notice and this permission notice appear -// in supporting documentation. Hewlett-Packard Company makes no -// representations about the suitability of this software for any -// purpose. It is provided "as is" without express or implied warranty. -// -// Copyright (c) 1996 -// Silicon Graphics Computer Systems, Inc. -// -// Permission to use, copy, modify, distribute and sell this software -// and its documentation for any purpose is hereby granted without fee, -// provided that the above copyright notice appear in all copies and -// that both that copyright notice and this permission notice appear -// in supporting documentation. Silicon Graphics makes no -// representations about the suitability of this software for any -// purpose. It is provided "as is" without express or implied warranty. -// diff --git a/vendor/range-v3/range-v3/README.md b/vendor/range-v3/range-v3/README.md deleted file mode 100644 index 0c52574f..00000000 --- a/vendor/range-v3/range-v3/README.md +++ /dev/null @@ -1,77 +0,0 @@ -range-v3 -======== - -Range library for C++14/17/20. This code was the basis of [a formal proposal](https://ericniebler.github.io/std/wg21/D4128.html) to add range support to the C++ standard library. That proposal evolved through a Technical Specification, and finally into [P0896R4 "The One Ranges Proposal"](https://wg21.link/p0896r4) which was merged into the C++20 working drafts in November 2018. - -About: ------- - -Ranges are an extension of the Standard Template Library that makes its iterators and algorithms more powerful by making them _composable_. Unlike other range-like solutions which seek to do away with iterators, in range-v3 ranges are an abstration layer _on top_ of iterators. - -Range-v3 is built on three pillars: Views, Actions, and Algorithms. The algorithms are the same as those with which you are already familiar in the STL, except that in range-v3 all the algorithms have overloads that take ranges in addition to the overloads that take iterators. Views are composable adaptations of ranges where the adaptation happens lazily as the view is iterated. And an action is an eager application of an algorithm to a container that mutates the container in-place and returns it for further processing. - -Views and actions use the pipe syntax (e.g., `rng | adapt1 | adapt2 | ...`) so your code is terse and readable from left to right. - -Documentation: --------------- - -Check out the (woefully incomplete) documentation [here](https://ericniebler.github.io/range-v3/). - -Other resources (mind the dates, the library probably has changed since then): - -- Usage: - - Talk: [CppCon 2015: Eric Niebler "Ranges for the Standard Library"](https://www.youtube.com/watch?v=mFUXNMfaciE), 2015. - - [A slice of Python in C++](http://ericniebler.com/2014/12/07/a-slice-of-python-in-c/), 07.12.2014. - - Actions (back then called [Container Algorithms](http://ericniebler.com/2014/11/23/container-algorithms/)), 23.11.2014. - - [Range comprehensions](http://ericniebler.com/2014/04/27/range-comprehensions/), 27.04.2014. - - [Input iterators vs input ranges](http://ericniebler.com/2013/11/07/input-iterators-vs-input-ranges/), 07.11.2013. - -- Design / Implementation: - - Rationale behind range-v3: [N4128: Ranges for the standard library Revision 1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4128.html), 2014. - - Ranges TS: [N4560: C++ Extensions for Ranges](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4560.pdf), 2015. - - Implementation of customization points in range-v3: - - [N4381: Suggested Design for Customization Points](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html), 2015. - - [P0386: Inline variables](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0386r0.pdf), 2016. - - [Customization Point Design in C++11 and Beyond](http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/), 2014. - - Proxy iterators in range-v3: - - [D0022: Proxy Iterators for the Ranges Extensions](https://ericniebler.github.io/std/wg21/D0022.html). - - [To Be or Not to Be (an Iterator)](http://ericniebler.com/2015/01/28/to-be-or-not-to-be-an-iterator/), 2015. - - [Iterators++: Part1](http://ericniebler.com/2015/02/03/iterators-plus-plus-part-1/), 2015. - - [Iterators++: Part2](http://ericniebler.com/2015/02/13/iterators-plus-plus-part-2/), 2015. - - [Iterators++: Part3](http://ericniebler.com/2015/03/03/iterators-plus-plus-part-3/), 2015. - - Metaprogramming utilities: - - See the [meta documentation](https://ericniebler.github.io/meta/index.html), the library has changed significantly since the [2014 blog post](http://ericniebler.com/2014/11/13/tiny-metaprogramming-library/). - - Concept emulation layer: [Concept checking in C++11](http://ericniebler.com/2013/11/23/concept-checking-in-c11/), 2013. - - [C++Now 2014: Eric Niebler "C++11 Library Design"](https://www.youtube.com/watch?v=zgOF4NrQllo), 2014. - -License: --------- - -Most of the source code in this project are mine, and those are under the Boost Software License. Parts are taken from Alex Stepanov's Elements of Programming, Howard Hinnant's libc++, and from the SGI STL. Please see the attached LICENSE file and the CREDITS file for the licensing and acknowledgments. - -Supported Compilers -------------------- - -The code is known to work on the following compilers: - -- clang 3.6.2 (or later) -- GCC 5.0.2 (or later) (C++14 "extended constexpr" support is poor before 6.1.) -- Clang/LLVM 6 (or later) on Windows (older versions may work - we haven't tested.) -- Visual Studio 2019 Preview 4 (or later) on Windows, with some caveats due to range-v3's strict conformance requirements: - - range-v3 needs `/std:c++17 /permissive-` - - range-v3 needs a fully conforming preprocessor, so `/experimental:preprocessor` is necessary. Note that the conforming preprocessor diagnoses `C5105` "macro expansion producing 'defined' has undefined behavior" in some of the Windows SDK headers, so you'll probably want to suppress that warning with `/wd5105`. - -[ Note: We've "retired" support for Clang/C2 with the VS2015 toolset (i.e., the `v140_clang_c2` toolset) which Microsoft no longer supports for C++ use. We no longer have CI runs, but haven't gone out of our way to break anything, so it will likely continue to work. ] - -**Development Status:** This code is fairly stable, well-tested, and suitable for casual use, although currently lacking documentation. _In general_, no promise is made about support or long-term stability. This code *will* evolve without regard to backwards compatibility. - -A notable exception is anything found within the `ranges::cpp20` namespace. Those components will change rarely or (preferably) never at all. - -**Build status** -- on Travis-CI: [![Travis Build Status](https://travis-ci.org/ericniebler/range-v3.svg?branch=master)](https://travis-ci.org/ericniebler/range-v3) -- on AppVeyor: [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/fwl9ymc2t6ukn9qj/branch/master?svg=true)](https://ci.appveyor.com/project/ericniebler/range-v3) - -Say Thanks! ------------ - -I do this work because I love it and because I love C++ and want it to be as excellent as I know it can be. If you like my work and are looking for a way to say thank you, you can leave a supportive comment on [my blog](http://ericniebler.com). Or you could leave me some kudos on my Open Hub range-v3 contribution page. Just click the **Give Kudos** button [here](https://www.openhub.net/p/range-v3/contributors/3053743222308608). diff --git a/vendor/range-v3/range-v3/TODO.md b/vendor/range-v3/range-v3/TODO.md deleted file mode 100644 index 55a44ed4..00000000 --- a/vendor/range-v3/range-v3/TODO.md +++ /dev/null @@ -1,26 +0,0 @@ -* Add contiguous iterator utilities. How about `is_contiguous_iterator` and `as_contiguous_range`: - - ``` - CPP_template(typename I, typename S)( - requires RandomAccessIterator && - SizedSentinel && - is_contiguous_iterator()) - subrange>> - as_contiguous_range(I begin, S end) - { - if(begin == end) - return {nullptr, nullptr}; - else - return {addressof(*begin), addressof(*begin) + (end - begin)}; - } - ``` -* Longer-term goals: - - Make `inplace_merge` work with forward iterators - - Make the sorting algorithms work with forward iterators - -* Maybe iterators are not necessarily countable. Is there a relation between - the ability to be able to subtract two iterators to find the distance, and - with the existence of a DistanceType associated type? Think of: - - counted iterators (subtractable regardless of traversal category) - - repeat_view iterators (*not* subtractable but could be random access otherwise) - - infinite ranges (only countable with an infinite precision integer which we lack) diff --git a/vendor/range-v3/range-v3/Version.cmake b/vendor/range-v3/range-v3/Version.cmake deleted file mode 100644 index a0866a16..00000000 --- a/vendor/range-v3/range-v3/Version.cmake +++ /dev/null @@ -1,5 +0,0 @@ -# To update the range-v3 version, from a *CLEAN* working directory, update the version numbers below. -# This makefile will generate a new version.hpp, *AMEND THE MOST RECENT COMMIT*, and git-tag the commit. -set(RANGE_V3_MAJOR 0) -set(RANGE_V3_MINOR 9) -set(RANGE_V3_PATCHLEVEL 1) diff --git a/vendor/range-v3/range-v3/WORKSPACE b/vendor/range-v3/range-v3/WORKSPACE deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/range-v3/range-v3/appveyor.yml b/vendor/range-v3/range-v3/appveyor.yml deleted file mode 100644 index 55c7c271..00000000 --- a/vendor/range-v3/range-v3/appveyor.yml +++ /dev/null @@ -1,59 +0,0 @@ -shallow_clone: true - -image: Visual Studio 2019 - -platform: - - x64_x86 - - x64 - -configuration: - - Debug - - Release - -environment: - matrix: - - CXX: clang-cl - CPP: latest - - - CXX: cl - CPP: 17 - - - CXX: cl - CPP: latest - -cache: - - C:\ninja-1.8.2 - -install: - - ps: | - if (![IO.File]::Exists("C:\ninja-1.8.2\ninja.exe")) { - Start-FileDownload 'https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-win.zip' - 7z x -y ninja-win.zip -oC:\ninja-1.8.2 - } - $env:PATH="C:\ninja-1.8.2;$env:PATH" - - for /f "tokens=1* delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath') do call "%%i\VC\Auxiliary\Build\vcvarsall.bat" %PLATFORM% - - cmake --version - - ninja --version - - clang-cl --version - - cl /Bv || exit 0 - -build_script: - - mkdir build && cd build - - ps: | - $env:CC=$env:CXX - $env:HEADER_CHECK=0 - if ($env:PLATFORM -eq "x64") { - if ($env:CONFIGURATION -eq "Debug") { - $env:HEADER_CHECK=1 - } - } elseif ($env:CXX -eq "clang-cl") { - $env:CXXFLAGS='-m32' - $env:CFLAGS='-m32' - } - - cmake .. -G Ninja -Wdev -DRANGE_V3_HEADER_CHECKS=%HEADER_CHECK% -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DRANGES_CXX_STD=%CPP% - - ninja - -test_script: - - ctest -j2 --output-on-failure - -deploy: off diff --git a/vendor/range-v3/range-v3/c_cpp_properties.json b/vendor/range-v3/range-v3/c_cpp_properties.json deleted file mode 100644 index febd71ce..00000000 --- a/vendor/range-v3/range-v3/c_cpp_properties.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "configurations": [ - { - "name": "Mac", - "includePath": [ - "/Users/eniebler/llvm-install/include/c++/v1", - "/usr/local/include", - "/Users/eniebler/llvm-install/lib/clang/6.0.0/include", - "/usr/include", - "${workspaceRoot}/include", - "/usr/local/opt/boost@1.67/incluse" - ], - "defines": [], - "intelliSenseMode": "clang-x64", - "browse": { - "path": [ - "/Users/eniebler/llvm-install/include/c++/v1", - "/usr/local/include", - "/Users/eniebler/llvm-install/lib/clang/6.0.0/include", - "/usr/include", - "${workspaceRoot}/include", - "/usr/local/opt/boost@1.67/incluse" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - }, - "macFrameworkPath": [ - "/System/Library/Frameworks", - "/Library/Frameworks" - ], - "compilerPath": "/Users/eniebler/llvm-install/bin/clang++", - "cStandard": "c11", - "cppStandard": "c++11", - "configurationProvider": "vector-of-bool.cmake-tools" - }, - { - "name": "Linux", - "includePath": [ - "/usr/include", - "/usr/local/include", - "${workspaceRoot}/include" - ], - "defines": [], - "intelliSenseMode": "clang-x64", - "browse": { - "path": [ - "/usr/include", - "/usr/local/include", - "${workspaceRoot}/include" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - }, - "cStandard": "c11", - "cppStandard": "c++11" - }, - { - "name": "Win32", - "includePath": [ - "${workspaceRoot}/include" - ], - "defines": [ - "_DEBUG", - "UNICODE" - ], - "intelliSenseMode": "msvc-x64", - "browse": { - "path": [ - "${workspaceRoot}/include" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - }, - "cStandard": "c11", - "cppStandard": "c++11" - } - ], - "version": 4 -} diff --git a/vendor/range-v3/range-v3/cmake/TestHeaders.cmake b/vendor/range-v3/range-v3/cmake/TestHeaders.cmake deleted file mode 100644 index 663c9f4e..00000000 --- a/vendor/range-v3/range-v3/cmake/TestHeaders.cmake +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright Louis Dionne 2013-2017 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) -# -# -# This CMake module provides a function generating a unit test to make sure -# that every public header can be included on its own. -# -# When a C++ library or application has many header files, it can happen that -# a header does not include all the other headers it depends on. When this is -# the case, it can happen that including that header file on its own will -# break the compilation. This CMake module generates a dummy executable -# comprised of many .cpp files, each of which includes a header file that -# is part of the public API. In other words, the executable is comprised -# of .cpp files of the form: -# -# #include -# -# and then exactly one `main` function. If this succeeds to compile, it means -# that the header can be included on its own, which is what clients expect. -# Otherwise, you have a problem. Since writing these dumb unit tests by hand -# is tedious and repetitive, you can use this CMake module to automate this -# task. - -# add_header_test( [EXCLUDE_FROM_ALL] [EXCLUDE excludes...] HEADERS headers...) -# -# Generates header-inclusion unit tests for all the specified headers. -# -# This function creates a target which builds a dummy executable including -# each specified header file individually. If this target builds successfully, -# it means that all the specified header files can be included individually. -# -# Parameters -# ---------- -# : -# The name of the target to generate. -# -# HEADERS headers: -# A list of header files to generate the inclusion tests for. All headers -# in this list must be represented as relative paths from the root of the -# include directory added to the compiler's header search path. In other -# words, it should be possible to include all headers in this list as -# -# #include <${header}> -# -# For example, for a library with the following structure: -# -# project/ -# doc/ -# test/ -# ... -# include/ -# boost/ -# hana.hpp -# hana/ -# transform.hpp -# tuple.hpp -# pair.hpp -# ... -# -# When building the unit tests for that library, we'll add `-I project/include' -# to the compiler's arguments. The list of public headers should therefore contain -# -# boost/hana.hpp -# boost/hana/transform.hpp -# boost/hana/tuple.hpp -# boost/hana/pair.hpp -# ... -# -# Usually, all the 'public' header files of a library should be tested for -# standalone inclusion. A header is considered 'public' if a client should -# be able to include that header on its own. -# -# [EXCLUDE excludes]: -# An optional list of headers or regexes for which no unit test should be -# generated. Basically, any header in the list specified by the `HEADERS` -# argument that matches anything in `EXCLUDE` will be skipped. -# -# [EXCLUDE_FROM_ALL]: -# If provided, the generated target is excluded from the 'all' target. -# -function(add_header_test target) - cmake_parse_arguments(ARGS "EXCLUDE_FROM_ALL" # options - "" # 1 value args - "HEADERS;EXCLUDE" # multivalued args - ${ARGN}) - if (NOT ARGS_HEADERS) - message(FATAL_ERROR "The `HEADERS` argument must be provided.") - endif() - - if (ARGS_EXCLUDE_FROM_ALL) - set(ARGS_EXCLUDE_FROM_ALL "EXCLUDE_FROM_ALL") - else() - set(ARGS_EXCLUDE_FROM_ALL "") - endif() - - foreach(header ${ARGS_HEADERS}) - set(skip FALSE) - foreach(exclude ${ARGS_EXCLUDE}) - if (${header} MATCHES ${exclude}) - set(skip TRUE) - break() - endif() - endforeach() - if (skip) - continue() - endif() - - get_filename_component(filename "${header}" NAME_WE) - get_filename_component(directory "${header}" DIRECTORY) - - set(source "${CMAKE_CURRENT_BINARY_DIR}/headers/${directory}/${filename}.cpp") - if (NOT EXISTS "${source}") - file(WRITE "${source}" "#include <${header}>") - endif() - list(APPEND sources "${source}") - endforeach() - - set(standalone_main "${CMAKE_CURRENT_BINARY_DIR}/headers/_standalone_main.cpp") - if (NOT EXISTS "${standalone_main}") - file(WRITE "${standalone_main}" "int main() { }") - endif() - add_executable(${target} - ${ARGS_EXCLUDE_FROM_ALL} - ${sources} - "${CMAKE_CURRENT_BINARY_DIR}/headers/_standalone_main.cpp" - ) -endfunction() diff --git a/vendor/range-v3/range-v3/cmake/aligned_new_probe.cpp b/vendor/range-v3/range-v3/cmake/aligned_new_probe.cpp deleted file mode 100644 index 148a3178..00000000 --- a/vendor/range-v3/range-v3/cmake/aligned_new_probe.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main() { - struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 4) S {}; - (void) ::operator new(sizeof(S), static_cast(alignof(S))); -} diff --git a/vendor/range-v3/range-v3/cmake/concepts_test_code.cpp b/vendor/range-v3/range-v3/cmake/concepts_test_code.cpp deleted file mode 100644 index 42aaa982..00000000 --- a/vendor/range-v3/range-v3/cmake/concepts_test_code.cpp +++ /dev/null @@ -1,19 +0,0 @@ -template -concept bool True = true; - -template -constexpr bool test(T) -{ - return false; -} - -template -constexpr bool test(T) -{ - return true; -} - -int main() -{ - static_assert(::test(42), ""); -} diff --git a/vendor/range-v3/range-v3/cmake/coro_test_code.cpp b/vendor/range-v3/range-v3/cmake/coro_test_code.cpp deleted file mode 100644 index 61e8efb2..00000000 --- a/vendor/range-v3/range-v3/cmake/coro_test_code.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include - -struct present -{ - struct promise_type - { - int result; - - present get_return_object() { return {*this}; } - std::experimental::suspend_never initial_suspend() { return {}; } - std::experimental::suspend_never final_suspend() { return {}; } - void return_value(int i) { result = i; } - void unhandled_exception() {} - }; - - promise_type& promise; - - bool await_ready() const { return true; } - void await_suspend(std::experimental::coroutine_handle<>) const {} - int await_resume() const { return promise.result; } -}; - -present f(int n) -{ - if (n < 2) - co_return 1; - else - co_return n * co_await f(n - 1); -} - -int main() -{ - return f(5).promise.result != 120; -} diff --git a/vendor/range-v3/range-v3/cmake/ranges_diagnostics.cmake b/vendor/range-v3/range-v3/cmake/ranges_diagnostics.cmake deleted file mode 100644 index 6c8ae612..00000000 --- a/vendor/range-v3/range-v3/cmake/ranges_diagnostics.cmake +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright Louis Dionne 2015 -# Copyright Gonzalo Brito Gadeschi 2015 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) -# -# Setup compiler flags (more can be set on a per-target basis or in -# subdirectories) - -# Enable all warnings: -ranges_append_flag(RANGES_HAS_WEVERYTHING -Weverything) -ranges_append_flag(RANGES_HAS_PEDANTIC -pedantic) -ranges_append_flag(RANGES_HAS_PEDANTIC_ERRORS -pedantic-errors) - -# Selectively disable those warnings that are not worth it: -ranges_append_flag(RANGES_HAS_WNO_CXX98_COMPAT -Wno-c++98-compat) -ranges_append_flag(RANGES_HAS_WNO_CXX98_COMPAT_PEDANTIC -Wno-c++98-compat-pedantic) -ranges_append_flag(RANGES_HAS_WNO_WEAK_VTABLES -Wno-weak-vtables) -ranges_append_flag(RANGES_HAS_WNO_PADDED -Wno-padded) -ranges_append_flag(RANGES_HAS_WNO_MISSING_PROTOTYPES -Wno-missing-prototypes) -ranges_append_flag(RANGES_HAS_WNO_MISSING_VARIABLE_DECLARATIONS -Wno-missing-variable-declarations) -ranges_append_flag(RANGES_HAS_WNO_DOCUMENTATION -Wno-documentation) -ranges_append_flag(RANGES_HAS_WNO_DOCUMENTATION_UNKNOWN_COMMAND -Wno-documentation-unknown-command) -ranges_append_flag(RANGES_HAS_WNO_OLD_STYLE_CAST -Wno-old-style-cast) - -if (RANGES_ENV_MACOSX) - ranges_append_flag(RANGES_HAS_WNO_GLOBAL_CONSTRUCTORS -Wno-global-constructors) - ranges_append_flag(RANGES_HAS_WNO_EXIT_TIME_DESTRUCTORS -Wno-exit-time-destructors) -endif() - -if (RANGES_CXX_COMPILER_GCC) - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "6.0") - ranges_append_flag(RANGES_HAS_WNO_STRICT_OVERFLOW -Wno-strict-overflow) - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0") - ranges_append_flag(RANGES_HAS_WNO_MISSING_FIELD_INITIALIZERS -Wno-missing-field-initializers) - endif() - elseif ((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.0") OR (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "7.0")) - ranges_append_flag(RANGES_HAS_WNO_NOEXCEPT_TYPE -Wno-noexcept-type) - endif() -endif() - -if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: test C++ flags: ${CMAKE_CXX_FLAGS}") -endif() diff --git a/vendor/range-v3/range-v3/cmake/ranges_env.cmake b/vendor/range-v3/range-v3/cmake/ranges_env.cmake deleted file mode 100644 index 6c90458c..00000000 --- a/vendor/range-v3/range-v3/cmake/ranges_env.cmake +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright Gonzalo Brito Gadeschi 2015 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) -# -# Detects the C++ compiler, system, build-type, etc. -include(CheckCXXCompilerFlag) - -if("x${CMAKE_CXX_COMPILER_ID}" MATCHES "x.*Clang") - if("x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC") - set (RANGES_CXX_COMPILER_CLANGCL TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: compiler is clang-cl.") - endif() - else() - set (RANGES_CXX_COMPILER_CLANG TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: compiler is clang.") - endif() - endif() -elseif(CMAKE_COMPILER_IS_GNUCXX) - set (RANGES_CXX_COMPILER_GCC TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: compiler is gcc.") - endif() -elseif("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") - set (RANGES_CXX_COMPILER_MSVC TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: compiler is msvc.") - endif() -else() - message(WARNING "[range-v3 warning]: unknown compiler ${CMAKE_CXX_COMPILER_ID} !") -endif() - -if(CMAKE_SYSTEM_NAME MATCHES "Darwin") - set (RANGES_ENV_MACOSX TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: system is MacOSX.") - endif() -elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") - set (RANGES_ENV_LINUX TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: system is Linux.") - endif() -elseif(CMAKE_SYSTEM_NAME MATCHES "Windows") - set (RANGES_ENV_WINDOWS TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: system is Windows.") - endif() -else() - message(WARNING "[range-v3 warning]: unknown system ${CMAKE_SYSTEM_NAME} !") -endif() - -if (RANGES_CXX_COMPILER_MSVC AND RANGES_CXX_STD LESS 17) - # MSVC is currently supported only in 17+ mode - set(RANGES_CXX_STD 17) -elseif(RANGES_CXX_STD LESS 14) - set(RANGES_CXX_STD 14) -endif() - -# Build type -set(RANGES_DEBUG_BUILD FALSE) -set(RANGES_RELEASE_BUILD FALSE) - -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(RANGES_DEBUG_BUILD TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: build type is debug.") - endif() -elseif(CMAKE_BUILD_TYPE STREQUAL "Release") - set(RANGES_RELEASE_BUILD TRUE) - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: build type is release.") - endif() -else() - message(WARNING "[range-v3 warning]: unknown build type, defaults to release!") - set(CMAKE_BUILD_TYPE "Release") - set(RANGES_RELEASE_BUILD TRUE) -endif() - -# Find Valgrind -find_program(MEMORYCHECK_COMMAND valgrind) -if(MEMORYCHECK_COMMAND) - set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full") - if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: valgrind found at path: ${MEMORYCHECK_COMMAND}") - endif() -else() - if (RANGES_VERBOSE_BUILD) - message(WARNING "[range-v3 warning]: valgrind not found!") - endif() -endif() - -find_package(Doxygen) -find_package(Git) diff --git a/vendor/range-v3/range-v3/cmake/ranges_flags.cmake b/vendor/range-v3/range-v3/cmake/ranges_flags.cmake deleted file mode 100644 index 1167e3d2..00000000 --- a/vendor/range-v3/range-v3/cmake/ranges_flags.cmake +++ /dev/null @@ -1,247 +0,0 @@ -# Copyright Louis Dionne 2015 -# Copyright Gonzalo Brito Gadeschi 2015 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) -# -# Setup compiler flags (more can be set on a per-target basis or in -# subdirectories) - -# Compilation flags -include(CheckCXXCompilerFlag) -macro(ranges_append_flag testname flag) - # As -Wno-* flags do not lead to build failure when there are no other - # diagnostics, we check positive option to determine their applicability. - # Of course, we set the original flag that is requested in the parameters. - string(REGEX REPLACE "^-Wno-" "-W" alt ${flag}) - check_cxx_compiler_flag(${alt} ${testname}) - if (${testname}) - add_compile_options(${flag}) - endif() -endmacro() - -# All compilation flags -# Language flag: version of the C++ standard to use -message(STATUS "[range-v3]: C++ std=${RANGES_CXX_STD}") -if (RANGES_CXX_COMPILER_CLANGCL OR RANGES_CXX_COMPILER_MSVC) - ranges_append_flag(RANGES_HAS_CXXSTDCOLON "/std:c++${RANGES_CXX_STD}") - set(RANGES_STD_FLAG "/std:c++${RANGES_CXX_STD}") - if (RANGES_CXX_COMPILER_CLANGCL) - ranges_append_flag(RANGES_HAS_FNO_MS_COMPATIBIILITY "-fno-ms-compatibility") - ranges_append_flag(RANGES_HAS_FNO_DELAYED_TEMPLATE_PARSING "-fno-delayed-template-parsing") - endif() - # Enable "normal" warnings and make them errors: - ranges_append_flag(RANGES_HAS_W3 /W3) - ranges_append_flag(RANGES_HAS_WX /WX) -else() - ranges_append_flag(RANGES_HAS_CXXSTD "-std=c++${RANGES_CXX_STD}") - set(RANGES_STD_FLAG "-std=c++${RANGES_CXX_STD}") - # Enable "normal" warnings and make them errors: - ranges_append_flag(RANGES_HAS_WALL -Wall) - ranges_append_flag(RANGES_HAS_WEXTRA -Wextra) - ranges_append_flag(RANGES_HAS_WERROR -Werror) -endif() - -if (RANGES_ENV_LINUX AND RANGES_CXX_COMPILER_CLANG) - # On linux libc++ re-exports the system math headers. The ones from libstdc++ - # use the GCC __extern_always_inline intrinsic which is not supported by clang - # versions 3.6, 3.7, 3.8, 3.9, 4.0, and current trunk 5.0 (as of 2017.04.13). - # - # This works around it by replacing __extern_always_inline with inline using a - # macro: - ranges_append_flag(RANGES_HAS_D__EXTERN_ALWAYS_INLINE -D__extern_always_inline=inline) -endif() - -# Deep integration support -if (RANGES_DEEP_STL_INTEGRATION) - if (RANGES_CXX_COMPILER_MSVC) - add_compile_options(/I "${PROJECT_SOURCE_DIR}/include/std") - else() - add_compile_options(-isystem "${PROJECT_SOURCE_DIR}/include/std") - endif() - add_compile_options(-DRANGES_DEEP_STL_INTEGRATION=1) -endif() - -# Template diagnostic flags -ranges_append_flag(RANGES_HAS_FDIAGNOSTIC_SHOW_TEMPLATE_TREE -fdiagnostics-show-template-tree) -ranges_append_flag(RANGES_HAS_FTEMPLATE_BACKTRACE_LIMIT "-ftemplate-backtrace-limit=0") -ranges_append_flag(RANGES_HAS_FMACRO_BACKTRACE_LIMIT "-fmacro-backtrace-limit=1") - -# Clang modules support -if (RANGES_MODULES) - ranges_append_flag(RANGES_HAS_MODULES -fmodules) - ranges_append_flag(RANGES_HAS_MODULE_MAP_FILE "-fmodule-map-file=${PROJECT_SOURCE_DIR}/include/module.modulemap") - ranges_append_flag(RANGES_HAS_MODULE_CACHE_PATH "-fmodules-cache-path=${PROJECT_BINARY_DIR}/module.cache") - if (RANGES_LIBCXX_MODULE) - ranges_append_flag(RANGES_HAS_LIBCXX_MODULE_MAP_FILE "-fmodule-map-file=${RANGES_LIBCXX_MODULE}") - endif() - if (RANGES_ENV_MACOSX) - ranges_append_flag(RANGES_HAS_NO_IMPLICIT_MODULE_MAPS -fno-implicit-module-maps) - endif() - if (RANGES_DEBUG_BUILD) - ranges_append_flag(RANGES_HAS_GMODULES -gmodules) - endif() -endif() - -# Sanitizer support: detect incompatible sanitizer combinations -if (RANGES_ASAN AND RANGES_MSAN) - message(FATAL_ERROR "[range-v3 error]: AddressSanitizer and MemorySanitizer are both enabled at the same time!") -endif() - -if (RANGES_MSAN AND RANGES_ENV_MACOSX) - message(FATAL_ERROR "[range-v3 error]: MemorySanitizer is not supported on MacOSX!") -endif() - -# AddressSanitizer support -if (RANGES_ASAN) - # This policy enables passing the linker flags to the linker when trying to - # test the features, which is required to successfully link ASan binaries - cmake_policy(SET CMP0056 NEW) - set (ASAN_FLAGS "") - if (RANGES_ENV_MACOSX) # LeakSanitizer not supported on MacOSX - set (ASAN_FLAGS "-fsanitize=address,signed-integer-overflow,shift,integer-divide-by-zero,implicit-signed-integer-truncation,implicit-integer-sign-change,undefined,nullability") - else() - if (RANGES_CXX_COMPILER_CLANG AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.0") - set (ASAN_FLAGS "-fsanitize=address") - else() - set (ASAN_FLAGS "-fsanitize=address,signed-integer-overflow,shift,integer-divide-by-zero,implicit-signed-integer-truncation,implicit-integer-sign-change,leak,nullability") - endif() - endif() - ranges_append_flag(RANGES_HAS_ASAN "${ASAN_FLAGS}") - if (RANGES_HAS_ASAN) #ASAN flags must be passed to the linker: - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${ASAN_FLAGS}") - endif() - ranges_append_flag(RANGES_HAS_SANITIZE_NO_RECOVER "-fno-sanitize-recover=all") - ranges_append_flag(RANGES_HAS_NO_OMIT_FRAME_POINTER -fno-omit-frame-pointer) -endif() - -# MemorySanitizer support -if (RANGES_MSAN) - # This policy enables passing the linker flags to the linker when trying to - # compile the examples, which is required to successfully link MSan binaries - cmake_policy(SET CMP0056 NEW) - ranges_append_flag(RANGES_HAS_MSAN "-fsanitize=memory") - ranges_append_flag(RANGES_HAS_MSAN_TRACK_ORIGINS -fsanitize-memory-track-origins) - ranges_append_flag(RANGES_HAS_SANITIZE_RECOVER_ALL "-fno-sanitize-recover=all") - ranges_append_flag(RANGES_HAS_NO_OMIT_FRAME_POINTER -fno-omit-frame-pointer) -endif() - -# Build types: -if (RANGES_DEBUG_BUILD AND RANGES_RELEASE_BUILD) - message(FATAL_ERROR "[range-v3 error] Cannot simultaneously generate debug and release builds!") -endif() - -if (RANGES_DEBUG_BUILD) - ranges_append_flag(RANGES_HAS_O0 -O0) - ranges_append_flag(RANGES_HAS_NO_INLINE -fno-inline) - ranges_append_flag(RANGES_HAS_STACK_PROTECTOR_ALL -fstack-protector-all) - ranges_append_flag(RANGES_HAS_G3 -g3) - # Clang can generate debug info tuned for LLDB or GDB - if (RANGES_CXX_COMPILER_CLANG) - if (RANGES_ENV_MACOSX) - ranges_append_flag(RANGES_HAS_GLLDB -glldb) - elseif(RANGES_ENV_LINUX) - ranges_append_flag(RANGES_HAS_GGDB -ggdb) - endif() - endif() -endif() - -if (RANGES_RELEASE_BUILD) - if (NOT RANGES_ASSERTIONS) - ranges_append_flag(RANGES_HAS_DNDEBUG -DNDEBUG) - endif() - if (NOT RANGES_ASAN AND NOT RANGES_MSAN) - # The quality of ASan and MSan error messages suffers if we disable the - # frame pointer, so leave it enabled when compiling with either of them: - ranges_append_flag(RANGES_HAS_OMIT_FRAME_POINTER -fomit-frame-pointer) - endif() - - ranges_append_flag(RANGES_HAS_OFAST -Ofast) - if (NOT RANGES_HAS_OFAST) - ranges_append_flag(RANGES_HAS_O2 -O2) - endif() - ranges_append_flag(RANGES_HAS_STRICT_ALIASING -fstrict-aliasing) - ranges_append_flag(RANGES_HAS_STRICT_VTABLE_POINTERS -fstrict-vtable-pointers) - ranges_append_flag(RANGES_HAS_FAST_MATH -ffast-math) - ranges_append_flag(RANGES_HAS_VECTORIZE -fvectorize) - - if (NOT RANGES_ENV_MACOSX) - # Sized deallocation is not available in MacOSX: - ranges_append_flag(RANGES_HAS_SIZED_DEALLOCATION -fsized-deallocation) - endif() - - if (RANGES_LLVM_POLLY) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -polly -mllvm -polly-vectorizer=stripmine") - endif() - - if (RANGES_CXX_COMPILER_CLANG AND (NOT (RANGES_INLINE_THRESHOLD EQUAL -1))) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -inline-threshold=${RANGES_INLINE_THRESHOLD}") - endif() -endif() - -if (RANGES_NATIVE) - ranges_append_flag(RANGES_HAS_MARCH_NATIVE "-march=native") - ranges_append_flag(RANGES_HAS_MTUNE_NATIVE "-mtune=native") -endif() - -include(CheckCXXSourceCompiles) - -set(CMAKE_REQUIRED_FLAGS ${RANGES_STD_FLAG}) -# Probe for library and compiler support for aligned new -file(READ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/aligned_new_probe.cpp" RANGE_V3_PROBE_CODE) -check_cxx_source_compiles("${RANGE_V3_PROBE_CODE}" RANGE_V3_ALIGNED_NEW_PROBE) -unset(RANGE_V3_PROBE_CODE) -unset(CMAKE_REQUIRED_FLAGS) -if (NOT RANGE_V3_ALIGNED_NEW_PROBE) - add_compile_options("-DRANGES_CXX_ALIGNED_NEW=0") -endif() - -# Probe for coroutine TS support -file(READ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/coro_test_code.cpp" RANGE_V3_PROBE_CODE) -if(RANGES_CXX_COMPILER_MSVC) - set(CMAKE_REQUIRED_FLAGS "/await") - check_cxx_source_compiles("${RANGE_V3_PROBE_CODE}" RANGES_HAS_AWAIT) - if(RANGES_HAS_AWAIT) - set(RANGE_V3_COROUTINE_FLAGS "/await") - endif() -elseif(RANGES_CXX_COMPILER_CLANG) - set(CMAKE_REQUIRED_FLAGS "-fcoroutines-ts ${RANGES_STD_FLAG}") - check_cxx_source_compiles("${RANGE_V3_PROBE_CODE}" RANGES_HAS_FCOROUTINES_TS) - if(RANGES_HAS_FCOROUTINES_TS) - set(RANGE_V3_COROUTINE_FLAGS "-fcoroutines-ts") - endif() -endif() -unset(CMAKE_REQUIRED_FLAGS) -unset(RANGE_V3_PROBE_CODE) -if (RANGE_V3_COROUTINE_FLAGS) - add_compile_options(${RANGE_V3_COROUTINE_FLAGS}) -endif() - -# Test for concepts support -file(READ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/concepts_test_code.cpp" RANGE_V3_PROBE_CODE) -if(RANGES_CXX_COMPILER_GCC OR RANGES_CXX_COMPILER_CLANG) - set(CMAKE_REQUIRED_FLAGS "-fconcepts ${RANGES_STD_FLAG}") - check_cxx_source_compiles("${RANGE_V3_PROBE_CODE}" RANGE_V3_HAS_FCONCEPTS) - if(RANGE_V3_HAS_FCONCEPTS) - set(RANGE_V3_CONCEPTS_FLAGS "-fconcepts") - endif() -endif() -unset(CMAKE_REQUIRED_FLAGS) -unset(RANGE_V3_PROBE_CODE) -if (RANGE_V3_CONCEPTS_FLAGS AND RANGES_PREFER_REAL_CONCEPTS) - add_compile_options(${RANGE_V3_CONCEPTS_FLAGS}) -endif() - -if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: C++ flags: ${CMAKE_CXX_FLAGS}") - message(STATUS "[range-v3]: C++ debug flags: ${CMAKE_CXX_FLAGS_DEBUG}") - message(STATUS "[range-v3]: C++ Release Flags: ${CMAKE_CXX_FLAGS_RELEASE}") - message(STATUS "[range-v3]: C++ Compile Flags: ${CMAKE_CXX_COMPILE_FLAGS}") - message(STATUS "[range-v3]: Compile options: ${COMPILE_OPTIONS_}") - message(STATUS "[range-v3]: C Flags: ${CMAKE_C_FLAGS}") - message(STATUS "[range-v3]: C Compile Flags: ${CMAKE_C_COMPILE_FLAGS}") - message(STATUS "[range-v3]: EXE Linker flags: ${CMAKE_EXE_LINKER_FLAGS}") - message(STATUS "[range-v3]: C++ Linker flags: ${CMAKE_CXX_LINK_FLAGS}") - message(STATUS "[range-v3]: MODULE Linker flags: ${CMAKE_MODULE_LINKER_FLAGS}") - get_directory_property(CMakeCompDirDefs COMPILE_DEFINITIONS) - message(STATUS "[range-v3]: Compile Definitions: ${CmakeCompDirDefs}") -endif() diff --git a/vendor/range-v3/range-v3/cmake/ranges_options.cmake b/vendor/range-v3/range-v3/cmake/ranges_options.cmake deleted file mode 100644 index f2a3efd2..00000000 --- a/vendor/range-v3/range-v3/cmake/ranges_options.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright Gonzalo Brito Gadeschi 2015 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) -# -# CMake options - -include(CMakeDependentOption) - -set(RANGES_CXX_STD 14 CACHE STRING "C++ standard version.") -option(RANGES_BUILD_CALENDAR_EXAMPLE "Builds the calendar example." ON) -option(RANGES_ASAN "Run the tests using AddressSanitizer." OFF) -option(RANGES_MSAN "Run the tests using MemorySanitizer." OFF) -option(RANGES_ASSERTIONS "Enable assertions." ON) -option(RANGES_DEBUG_INFO "Include debug information in the binaries." ON) -option(RANGES_MODULES "Enables use of Clang modules (experimental)." OFF) -option(RANGES_NATIVE "Enables -march/-mtune=native." ON) -option(RANGES_VERBOSE_BUILD "Enables debug output from CMake." OFF) -option(RANGES_LLVM_POLLY "Enables LLVM Polly." OFF) -option(RANGES_PREFER_REAL_CONCEPTS - "Use real concepts instead of emulation if the compiler supports it" - ON) -option(RANGES_DEEP_STL_INTEGRATION - "Hijacks the primary std::iterator_traits template to emulate the C++20 std::ranges:: behavior." - OFF) -option(RANGE_V3_HEADER_CHECKS - "Build the Range-v3 header checks and integrate with ctest" - OFF) -set(RANGES_INLINE_THRESHOLD -1 CACHE STRING "Force a specific inlining threshold.") - -# Enable verbose configure when passing -Wdev to CMake -if (DEFINED CMAKE_SUPPRESS_DEVELOPER_WARNINGS AND - NOT CMAKE_SUPPRESS_DEVELOPER_WARNINGS) - set(RANGES_VERBOSE_BUILD ON) -endif() - -if (RANGES_VERBOSE_BUILD) - message(STATUS "[range-v3]: verbose build enabled.") -endif() - -CMAKE_DEPENDENT_OPTION(RANGE_V3_TESTS - "Build the Range-v3 tests and integrate with ctest" - ON "${is_standalone}" OFF) - -CMAKE_DEPENDENT_OPTION(RANGE_V3_EXAMPLES - "Build the Range-v3 examples and integrate with ctest" - ON "${is_standalone}" OFF) - -CMAKE_DEPENDENT_OPTION(RANGE_V3_PERF - "Build the Range-v3 performance benchmarks" - ON "${is_standalone}" OFF) - -CMAKE_DEPENDENT_OPTION(RANGE_V3_DOCS - "Build the Range-v3 documentation" - ON "${is_standalone}" OFF) - -mark_as_advanced(RANGE_V3_PERF) diff --git a/vendor/range-v3/range-v3/cmake/readme.md b/vendor/range-v3/range-v3/cmake/readme.md deleted file mode 100644 index 76c74487..00000000 --- a/vendor/range-v3/range-v3/cmake/readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# CMake files overview: - -- `ranges_options.cmake`: All options to configure the library. -- `ranges_env.cmake`: Detects the environment: operating system, compiler, build-type, ... -- `ranges_flags.cmake`: Sets up all compiler flags. diff --git a/vendor/range-v3/range-v3/conanfile.py b/vendor/range-v3/range-v3/conanfile.py deleted file mode 100644 index 4ea163bb..00000000 --- a/vendor/range-v3/range-v3/conanfile.py +++ /dev/null @@ -1,35 +0,0 @@ -# Range v3 library -# -# Copyright Luis Martinez de Bartolome Izquierdo 2016 -# -# Use, modification and distribution is subject to the -# Boost Software License, Version 1.0. (See accompanying -# file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) -# -# Project home: https://github.com/ericniebler/range-v3 -# - -from conans import ConanFile, CMake - -class Rangev3Conan(ConanFile): - name = "range-v3" - version = "0.9.0" - license = "Boost Software License - Version 1.0 - August 17th, 2003" - url = "https://github.com/ericniebler/range-v3" - description = """Experimental range library for C++11/14/17""" - # No settings/options are necessary, this is header only - exports_sources = "include*", "LICENSE.txt", "CMakeLists.txt", "cmake/*", "Version.cmake", "version.hpp.in" - no_copy_source = True - - def package(self): - cmake = CMake(self) - cmake.definitions["RANGE_V3_TESTS"] = "OFF" - cmake.definitions["RANGE_V3_EXAMPLES"] = "OFF" - cmake.definitions["RANGE_V3_PERF"] = "OFF" - cmake.definitions["RANGE_V3_DOCS"] = "OFF" - cmake.definitions["RANGE_V3_HEADER_CHECKS"] = "OFF" - cmake.configure() - cmake.install() - - self.copy("LICENSE.txt", dst="licenses", ignore_case=True, keep_path=False) diff --git a/vendor/range-v3/range-v3/doc/CMakeLists.txt b/vendor/range-v3/range-v3/doc/CMakeLists.txt deleted file mode 100644 index 60234118..00000000 --- a/vendor/range-v3/range-v3/doc/CMakeLists.txt +++ /dev/null @@ -1,87 +0,0 @@ -#============================================================================= -# Setup the documentation -#============================================================================= -if (NOT DOXYGEN_FOUND) - message(STATUS - "Doxygen not found; the 'doc' and 'gh-pages.{clean,copy,update}' targets " - "will be unavailable.") - return() -endif() - -set(CMAKE_FOLDER "doc") - -configure_file(Doxyfile.in Doxyfile @ONLY) -add_custom_target(doc.check - COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile - COMMENT "Running Doxygen to validate the documentation" - VERBATIM -) -set_target_properties(doc.check - PROPERTIES FOLDER ${CMAKE_FOLDER} -) - -# if (NOT TARGET benchmarks) -# message(STATUS -# "The 'benchmarks' target is not available; the 'doc' and " -# "'gh-pages.{clean,copy,update}' targets will be unavailable. " -# "The 'doc.check' target can still be used to generate the " -# "documentation to check for errors/warnings.") -# return() -# endif() - -add_custom_target(doc - COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile - COMMENT "Generating API documentation with Doxygen" -# DEPENDS benchmarks - VERBATIM -) -set_target_properties(doc - PROPERTIES FOLDER ${CMAKE_FOLDER} -) - -if (NOT GIT_FOUND) - message(STATUS - "Git was not found; the 'gh-pages.{clean,copy,update}' targets " - "will be unavailable.") - return() -endif() - -add_custom_target(gh-pages.clean - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/clean-gh-pages.cmake - COMMAND ${CMAKE_COMMAND} -E remove_directory search - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/gh-pages - COMMENT "Cleaning up doc/gh-pages" - VERBATIM -) -set_target_properties(gh-pages.clean - PROPERTIES FOLDER ${CMAKE_FOLDER} -) - -add_custom_target(gh-pages.copy - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/html ${CMAKE_CURRENT_LIST_DIR}/gh-pages - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/gh-pages - COMMENT "Copying the documentation from ${CMAKE_CURRENT_BINARY_DIR}/html to doc/gh-pages" - DEPENDS doc gh-pages.clean - VERBATIM -) -set_target_properties(gh-pages.copy - PROPERTIES FOLDER ${CMAKE_FOLDER} -) - -execute_process( - COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_SOURCE_DIR} rev-parse --short HEAD - OUTPUT_VARIABLE RANGE_V3_GIT_SHORT_SHA - OUTPUT_STRIP_TRAILING_WHITESPACE -) - -add_custom_target(gh-pages.update - COMMAND ${GIT_EXECUTABLE} add --all . - COMMAND ${GIT_EXECUTABLE} commit -m "Update to ${RANGE_V3_GIT_SHORT_SHA}" - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/gh-pages - COMMENT "Updating the gh-pages branch with freshly built documentation" - DEPENDS gh-pages.copy - VERBATIM -) -set_target_properties(gh-pages.update - PROPERTIES FOLDER ${CMAKE_FOLDER} -) diff --git a/vendor/range-v3/range-v3/doc/Doxyfile.in b/vendor/range-v3/range-v3/doc/Doxyfile.in deleted file mode 100644 index 692a4559..00000000 --- a/vendor/range-v3/range-v3/doc/Doxyfile.in +++ /dev/null @@ -1,174 +0,0 @@ -PROJECT_NAME = "Range-v3" -PROJECT_BRIEF = "Range algorithms, views, and actions for the Standard Library" -PROJECT_LOGO = -PROJECT_NUMBER = - -STRIP_FROM_PATH = @Range-v3_SOURCE_DIR@/include -BUILTIN_STL_SUPPORT = YES -STRIP_FROM_INC_PATH = @Range-v3_SOURCE_DIR@/include -ALIASES = -ENABLED_SECTIONS = - - -# Resources -OUTPUT_DIRECTORY = -INPUT = @Range-v3_SOURCE_DIR@/include \ - @Range-v3_SOURCE_DIR@/doc/index.md \ - @Range-v3_SOURCE_DIR@/doc/examples.md \ - @Range-v3_SOURCE_DIR@/doc/release_notes.md -FILE_PATTERNS = *.hpp *.md -RECURSIVE = YES -EXCLUDE = @Range-v3_SOURCE_DIR@/include/range/v3/detail \ - @Range-v3_SOURCE_DIR@/include/range/v3/algorithm/aux_ \ - @Range-v3_SOURCE_DIR@/include/range/v3/algorithm/tagspec.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/at.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/back.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/begin_end.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/data.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/distance.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/empty.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/front.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/getlines.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/index.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/istream_range.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/iterator_range.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/range_access.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/range_concepts.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/range_traits.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/size.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/span.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/to_container.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/to_container.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/associated_types.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/basic_iterator.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/common_iterator.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/concepts.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/counted_iterator.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/dangling.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/functional.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/infinity.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/invoke.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/iterator_concepts.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/iterator_traits.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/iterator.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/nullptr_v.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/semiregular.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/tagged_pair.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/tagged_tuple.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/unreachable.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/utility/view_adaptor.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/view_adaptor.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/view_facade.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/view_interface.hpp \ - @Range-v3_SOURCE_DIR@/include/range/v3/view/bounded.hpp -EXAMPLE_PATH = @Range-v3_SOURCE_DIR@/example \ - @Range-v3_SOURCE_DIR@/test -EXAMPLE_RECURSIVE = YES -IMAGE_PATH = @Range-v3_BINARY_DIR@/image -WARN_IF_UNDOCUMENTED = NO - -SHOW_GROUPED_MEMB_INC = YES -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -JAVADOC_AUTOBRIEF = YES -QT_AUTOBRIEF = YES -MULTILINE_CPP_IS_BRIEF = YES -INHERIT_DOCS = NO -SEPARATE_MEMBER_PAGES = NO -DISTRIBUTE_GROUP_DOC = NO -SUBGROUPING = NO -INLINE_GROUPED_CLASSES = NO -INLINE_SIMPLE_STRUCTS = NO - -# Generated formats -GENERATE_HTML = YES -GENERATE_LATEX = NO - - -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST = YES -SHOW_USED_FILES = NO -SHOW_FILES = YES -SHOW_NAMESPACES = YES -LAYOUT_FILE = @Range-v3_SOURCE_DIR@/doc/layout.xml - - -CLASS_DIAGRAMS = YES -HAVE_DOT = NO - -HIDE_UNDOC_RELATIONS = NO -HIDE_UNDOC_MEMBERS = NO -HIDE_UNDOC_CLASSES = NO -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -HIDE_SCOPE_NAMES = NO -SHOW_INCLUDE_FILES = NO -FORCE_LOCAL_INCLUDES = NO -INLINE_INFO = NO -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = YES -SORT_MEMBERS_CTORS_1ST = NO -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = YES - - -ALPHABETICAL_INDEX = NO -COLS_IN_ALPHA_INDEX = 1 - -# Preprocessing -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = YES -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = @Range-v3_SOURCE_DIR@/include -INCLUDE_FILE_PATTERNS = -PREDEFINED = RANGES_DOXYGEN_INVOKED=1 \ - META_DOXYGEN_INVOKED=1 \ - CPP_DOXYGEN_INVOKED=1 \ - "RANGES_INLINE_VARIABLE(T,N)=inline constexpr T N{};" -SKIP_FUNCTION_MACROS = NO - -# Source browsing -SOURCE_BROWSER = NO -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = YES -REFERENCES_RELATION = YES -REFERENCES_LINK_SOURCE = YES -USE_HTAGS = NO -VERBATIM_HEADERS = NO -# CLANG_ASSISTED_PARSING = NO -# CLANG_OPTIONS = - -# HTML output -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_EXTRA_STYLESHEET = -HTML_EXTRA_FILES = -HTML_COLORSTYLE_HUE = 75 # 0 - 359 -HTML_COLORSTYLE_SAT = 100 # 0 - 255 -HTML_COLORSTYLE_GAMMA = 80 -HTML_TIMESTAMP = NO -HTML_DYNAMIC_SECTIONS = YES -HTML_INDEX_NUM_ENTRIES = 0 # Fully expand trees in the Indexes by default -DISABLE_INDEX = YES -GENERATE_TREEVIEW = YES -TREEVIEW_WIDTH = 270 -EXT_LINKS_IN_WINDOW = NO -FORMULA_FONTSIZE = 10 -FORMULA_TRANSPARENT = YES -SEARCHENGINE = YES - -# Mathjax (HTML only) -USE_MATHJAX = NO -MATHJAX_FORMAT = HTML-CSS -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest -MATHJAX_EXTENSIONS = -MATHJAX_CODEFILE = diff --git a/vendor/range-v3/range-v3/doc/clean-gh-pages.cmake b/vendor/range-v3/range-v3/doc/clean-gh-pages.cmake deleted file mode 100644 index 8fb22f48..00000000 --- a/vendor/range-v3/range-v3/doc/clean-gh-pages.cmake +++ /dev/null @@ -1,4 +0,0 @@ -FILE(GLOB gh_files "*.html" "*.js" "*.css" "*.png") -IF( gh_files ) - execute_process( COMMAND ${CMAKE_COMMAND} -E remove ${gh_files} ) -ENDIF() diff --git a/vendor/range-v3/range-v3/doc/examples.md b/vendor/range-v3/range-v3/doc/examples.md deleted file mode 100644 index 29ba0547..00000000 --- a/vendor/range-v3/range-v3/doc/examples.md +++ /dev/null @@ -1,62 +0,0 @@ -Examples -======== - -\section example-algorithms Examples: Algorithms - -\subsection example-hello Hello, Ranges! - -\snippet hello.cpp hello - -\subsection example-any-all-none any_of, all_of, none_of - -\snippet any_all_none_of.cpp any_all_none_of - -\subsection example-count count - -\snippet count.cpp count - -\subsection example-count_if count_if - -\snippet count_if.cpp count_if - -\subsection example-find find, find_if, find_if_not on sequence containers - -\snippet find.cpp find - -\subsection example-for_each-seq for_each on sequence containers - -\snippet for_each_sequence.cpp for_each_sequence - -\subsection example-for_each-assoc for_each on associative containers - -\snippet for_each_assoc.cpp for_each_assoc - -\subsection example-is_sorted is_sorted - -\snippet is_sorted.cpp is_sorted - -\section example-views Examples: Views - -\subsection example-filter-transform Filter and transform - -\snippet filter_transform.cpp filter_transform - -\subsection example-accumulate-ints Generate ints and accumulate - -\snippet accumulate_ints.cpp accumulate_ints - -\subsection example-comprehension-conversion Convert a range comprehension to a vector - -\snippet comprehension_conversion.cpp comprehension_conversion - -\section example-actions Examples: Actions - -\subsection example-sort-unique Remove non-unique elements from a container - -\snippet sort_unique.cpp sort_unique - -\section example-gestalt Examples: Putting it all together - -\subsection example-calendar Calendar - -\snippet calendar.cpp calendar diff --git a/vendor/range-v3/range-v3/doc/index.md b/vendor/range-v3/range-v3/doc/index.md deleted file mode 100644 index 933ba270..00000000 --- a/vendor/range-v3/range-v3/doc/index.md +++ /dev/null @@ -1,950 +0,0 @@ -User Manual {#mainpage} -=========== - -\tableofcontents - -\section tutorial-preface Preface - --------------------------------------------- -Range library for C++14/17/20. This code is the basis of [the range support in C++20](http://eel.is/c++draft/#ranges). - -**Development Status:** - -This code is fairly stable, well-tested, and suitable for casual use, although -currently lacking documentation. No promise is made about support or long-term -stability. This code *will* evolve without regard to backwards compatibility. - -A notable exception is anything found within the `ranges::cpp20` namespace. -Those components will change rarely or (preferably) never at all. - -\subsection tutorial-installation Installation - --------------------------------------------- -This library is header-only. You can get the source code from the -[range-v3 repository](https://github.com/ericniebler/range-v3) on github. To -compile with Range-v3, just `#include` the individual headers you want. - -This distribution actually contains three separate header-only libraries: - -* include/concepts/... contains the Concepts Portability Preprocessor, or - CPP, which is a set of macros for defining and using concept checks, - regardless of whether your compiler happens to support the C++20 concepts - language feature or not. -* include/meta/... contains the Meta Library, which is a set of - meta-programming utilities for processing types and lists of types at compile - time. -* include/range/... contains the Range-v3 library, as described below. - -The Range-v3 library is physically structured in directories by feature group: - -* include/range/v3/actions/... contains _actions_, or composable - components that operate eagerly on containers and return the mutated container - for further actions. -* include/range/v3/algorithms/... contains all the STL _algorithms_ with - overloads that accept ranges, in addition to the familiar overloads that take iterators. -* include/range/v3/functional/... contains many generally useful - components that would be familiar to functional programmers. -* include/range/v3/iterator/... contains the definitions of many useful - iterators and iterator-related concepts and utilities. -* include/range/v3/numeric/... contains numeric algorithms corresponding - to those found in the standard `` header. -* include/range/v3/range/... contains range-related utilities, such as - `begin`, `end`, and `size`, range traits and concepts, and conversions to - containers. -* include/range/v3/utility/... contains a miscellaneous assortment of - reusable code. -* include/range/v3/view/... contains _views_, or composable - components that operate lazily on ranges and that themselves return ranges - that can be operated upon with additional view adaptors. - -\subsection tutorial-license License - --------------------------------------------- -Most of the source code in this project are mine, and those are under the Boost -Software License. Parts are taken from Alex Stepanov's Elements of Programming, -Howard Hinnant's libc++, and from the SGI STL. Please see the attached LICENSE -file and the CREDITS file for the licensing and acknowledgements. - -\subsection tutorial-compilers Supported Compilers - --------------------------------------------------------------------------------- -The code is known to work on the following compilers: - -- clang 3.6.2 -- GCC 5.0.2 -- MSVC VS2017 15.9 (`_MSC_VER >= 1916`), with `/std:c++17 /permissive-` - -\section tutorial-quick-start Quick Start - --------------------------------------------------------------------------------- -Range-v3 is a generic library that augments the existing standard library with -facilities for working with *ranges*. A range can be loosely thought of a pair -of iterators, although they need not be implemented that way. Bundling begin/end -iterators into a single object brings several benefits: convenience, -composability, and correctness. - -**Convenience** - -It's more convenient to pass a single range object to an algorithm than separate -begin/end iterators. Compare: - -~~~~~~~{.cpp} - std::vector v{/*...*/}; - std::sort( v.begin(), v.end() ); -~~~~~~~ - -with - -~~~~~~~{.cpp} - std::vector v{/*...*/}; - ranges::sort( v ); -~~~~~~~ - -Range-v3 contains full implementations of all the standard algorithms with -range-based overloads for convenience. - -**Composability** - -Having a single range object permits *pipelines* of operations. In a pipeline, a -range is lazily adapted or eagerly mutated in some way, with the result -immediately available for further adaptation or mutation. Lazy adaption is -handled by *views*, and eager mutation is handled by *actions*. - -For instance, the below uses _views_ to filter a container using a predicate -and transform the resulting range with a function. Note that the underlying -data is `const` and is not mutated by the views. - -~~~~~~~{.cpp} - std::vector const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - using namespace ranges; - auto rng = vi | views::remove_if([](int i){ return i % 2 == 1; }) - | views::transform([](int i){ return std::to_string(i); }); - // rng == {"2","4","6","8","10"}; -~~~~~~~ - -In the code above, `rng` simply stores a reference to the underlying data and -the filter and transformation functions. No work is done until `rng` is -iterated. - -In contrast, _actions_ do their work eagerly, but they also compose. Consider -the code below, which reads some data into a vector, sorts it, and makes it -unique. - -~~~~~~~{.cpp} - extern std::vector read_data(); - using namespace ranges; - std::vector vi = read_data() | actions::sort | actions::unique; -~~~~~~~ - -Unlike views, with actions each step in the pipeline (`actions::sort` and -`actions::unique`) accepts a container _by value_, mutates it in place, and -returns it. - -**Correctness** - -Whether you are using views or actions, you are operating on data in a pure -functional, declarative style. You rarely need to trouble yourself with -iterators, although they are there under the covers should you need them. - -By operating declaratively and functionally instead of imperatively, we reduce -the need for overt state manipulation and branches and loops. This brings down -the number of states your program can be in, which brings down your bug counts. - -In short, if you can find a way to express your solution as a composition of -functional transformations on your data, you can make your code _correct by -construction_. - -\subsection tutorial-views Views - --------------------------------------------------------------------------------- -As described above, the big advantage of ranges over iterators is their -composability. They permit a functional style of programming where data is -manipulated by passing it through a series of combinators. In addition, the -combinators can be *lazy*, only doing work when the answer is requested, and -*purely functional*, without mutating the original data. This makes it easier to -reason about your code. - -A _view_ is a lightweight wrapper that presents a view of an underlying sequence -of elements in some custom way without mutating or copying it. Views are cheap -to create and copy and have non-owning reference semantics. Below are some -examples that use views: - -Filter a container using a predicate and transform it. - -~~~~~~~{.cpp} - std::vector const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - using namespace ranges; - auto rng = vi | views::remove_if([](int i){return i % 2 == 1;}) - | views::transform([](int i){return std::to_string(i);}); - // rng == {"2","4","6","8","10"}; -~~~~~~~ - -Generate an infinite list of integers starting at 1, square them, take the first -10, and sum them: - -~~~~~~~{.cpp} - using namespace ranges; - int sum = accumulate(views::ints(1) - | views::transform([](int i){return i*i;}) - | views::take(10), 0); -~~~~~~~ - -Generate a sequence on the fly with a range comprehension and initialize a -vector with it: - -~~~~~~~{.cpp} - using namespace ranges; - auto vi = - views::for_each(views::ints(1, 10), [](int i) { - return yield_from(views::repeat_n(i, i)); - }) - | to(); - // vi == {1,2,2,3,3,3,4,4,4,4,5,5,5,5,5,...} -~~~~~~~ - -### View const-ness - -Logically, a view is a factory for iterators, but in practice a view is often -implemented as a state machine, with the state stored within the view object -itself (to keep iterators small) and mutated as the view is iterated. Because -the view contains mutable state, many views lack a `const`-qualified -`begin()`/`end()`. When `const` versions of `begin()`/`end()` are provided, they -are truly `const`; that is, thread-safe. - -Since views present the same interface as containers, the temptation is to think -they behave like containers with regard to `const`-ness. This is not the case. -Their behavior with regards to `const`-ness is similar to iterators and -pointers. - -The `const`-ness of a view is not related to the `const`-ness of the underlying -data. A non-`const` view may refer to elements that are themselves `const`, and -_vice versa_. This is analogous to pointers; an `int* const` is a `const` -pointer to a mutable `int`, and a `int const*` is a non-`const` pointer to a -`const` `int`. - -Use non-`const` views whenever possible. If you need thread-safety, work with -view copies in threads; don't share. - -### View validity - -Any operation on the underlying range that invalidates its iterators or -sentinels will also invalidate any view that refers to any part of that range. -Additionally, some views (_e.g._, `views::filter`), are invalidated when the -underlying elements of the range are mutated. It is best to recreate a view -after any operation that may have mutated the underlying range. - -### List of range views - -Below is a list of the lazy range combinators, or views, that Range-v3 -provides, and a blurb about how each is intended to be used. - -
-
\link ranges::views::addressof_fn `views::addressof`\endlink
-
Given a source range of lvalue references, return a new view that is the result of taking `std::addressof` of each.
-
\link ranges::views::adjacent_filter_fn `views::adjacent_filter`\endlink
-
For each pair of adjacent elements in a source range, evaluate the specified binary predicate. If the predicate evaluates to false, the second element of the pair is removed from the result range; otherwise, it is included. The first element in the source range is always included. (For instance, `adjacent_filter` with `std::not_equal_to` filters out all the non-unique elements.)
-
\link ranges::views::adjacent_remove_if_fn `views::adjacent_remove_if`\endlink
-
For each pair of adjacent elements in a source range, evaluate the specified binary predicate. If the predicate evaluates to true, the first element of the pair is removed from the result range; otherwise, it is included. The last element in the source range is always included.
-
\link ranges::views::all_fn `views::all`\endlink
-
Return a range containing all the elements in the source. Useful for converting containers to ranges.
-
\link ranges::any_view `any_view(rng)`\endlink
-
Type-erased range of elements with value type `T`; can store _any_ range with this value type.
-
\link ranges::views::c_str_fn `views::c_str`\endlink
-
View a `\0`-terminated C string (e.g. from a `const char*`) as a range.
-
\link ranges::views::cartesian_product_fn `views::cartesian_product`\endlink
-
Enumerates the n-ary cartesian product of `n` ranges, i.e., generates all `n`-tuples `(e1, e2, ... , en)` where `e1` is an element of the first range, `e2` is an element of the second range, etc.
-
\link ranges::views::chunk_fn `views::chunk`\endlink
-
Given a source range and an integer *N*, produce a range of contiguous ranges where each inner range has *N* contiguous elements. The final range may have fewer than *N* elements.
-
\link ranges::views::common_fn `views::common`\endlink
-
Convert the source range to a *common* range, where the type of the `end` is the same as the `begin`. Useful for calling algorithms in the `std::` namespace.
-
\link ranges::views::concat_fn `views::concat`\endlink
-
Given *N* source ranges, produce a result range that is the concatenation of all of them.
-
\link ranges::views::const_fn `views::const_`\endlink
-
Present a `const` view of a source range.
-
\link ranges::views::counted_fn `views::counted`\endlink
-
Given an iterator `it` and a count `n`, create a range that starts at `it` and includes the next `n` elements.
-
\link ranges::views::cycle_fn `views::cycle`\endlink
-
Returns an infinite range that endlessly repeats the source range.
-
\link ranges::views::delimit_fn `views::delimit`\endlink
-
Given a source range and a value, return a new range that ends either at the end of the source or at the first occurrence of the value, whichever comes first. Alternatively, `views::delimit` can be called with an iterator and a value, in which case it returns a range that starts at the specified position and ends at the first occurrence of the value.
-
\link ranges::views::drop_fn `views::drop`\endlink
-
Given a source range and an integral count, return a range consisting of all but the first *count* elements from the source range, or an empty range if it has fewer elements.
-
\link ranges::views::drop_last_fn `views::drop_last`\endlink
-
Given a source range and an integral count, return a range consisting of all but the last *count* elements from the source range, or an empty range if it has fewer elements.
-
\link ranges::views::drop_exactly_fn `views::drop_exactly`\endlink
-
Given a source range and an integral count, return a range consisting of all but the first *count* elements from the source range. The source range must have at least that many elements.
-
\link ranges::views::drop_while_fn `views::drop_while`\endlink
-
Remove elements from the front of a range that satisfy a unary predicate.
-
\link ranges::views::empty() `views::empty`\endlink
-
Create an empty range with a given value type.
-
\link ranges::views::enumerate() `views::enumerate`\endlink
-
Pair each element of a range with its index.
-
\link ranges::views::filter_fn `views::filter`\endlink
-
Given a source range and a unary predicate, filter the elements that satisfy the predicate. (For users of Boost.Range, this is like the `filter` adaptor.)
-
\link ranges::views::for_each_fn `views::for_each`\endlink
-
Lazily applies an unary function to each element in the source range that returns another range (possibly empty), flattening the result.
-
\link ranges::views::generate_fn `views::generate`\endlink
-
Given a nullary function, return an infinite range whose elements are generated with the function.
-
\link ranges::views::generate_n_fn `views::generate_n`\endlink
-
Given a nullary function and a count, return a range that generates the requested number of elements by calling the function.
-
\link ranges::views::group_by_fn `views::group_by`\endlink
-
Given a source range and a binary predicate, return a range of ranges where each range contains contiguous elements from the source range such that the following condition holds: for each element in the range apart from the first, when that element and the first element are passed to the binary predicate, the result is true. In essence, `views::group_by` *groups* contiguous elements together with a binary predicate.
-
\link ranges::views::indirect_fn `views::indirect`\endlink
-
Given a source range of readable values (e.g. pointers or iterators), return a new view that is the result of dereferencing each.
-
\link ranges::views::intersperse_fn `views::intersperse`\endlink
-
Given a source range and a value, return a new range where the value is inserted between contiguous elements from the source.
-
\link ranges::views::ints_fn `views::ints`\endlink
-
Generate a range of monotonically increasing `int`s. When used without arguments, it generates the quasi-infinite range [0,1,2,3...]. It can also be called with a lower bound, or with a lower and upper bound (exclusive). An inclusive version is provided by `closed_ints`.
-
\link ranges::views::iota_fn `views::iota`\endlink
-
A generalization of `views::ints` that generates a sequence of monotonically increasing values of any incrementable type. When specified with a single argument, the result is an infinite range beginning at the specified value. With two arguments, the values are assumed to denote a half-open range.
-
\link ranges::views::join_fn `views::join`\endlink
-
Given a range of ranges, join them into a flattened sequence of elements. Optionally, you can specify a value or a range to be inserted between each source range.
-
\link ranges::views::keys_fn `views::keys`\endlink
-
Given a range of `pair`s (like a `std::map`), return a new range consisting of just the first element of the `pair`.
-
\link ranges::views::linear_distribute_fn `views::linear_distribute`\endlink
-
Distributes `n` values linearly in the closed interval `[from, to]` (the end points are always included). If `from == to`, returns `n`-times `to`, and if `n == 1` it returns `to`.
-
\link ranges::views::move_fn `views::move`\endlink
-
Given a source range, return a new range where each element has been has been cast to an rvalue reference.
-
\link ranges::views::partial_sum_fn `views::partial_sum`\endlink
-
Given a range and a binary function, return a new range where the *N*th element is the result of applying the function to the *N*th element from the source range and the (N-1)th element from the result range.
-
\link ranges::views::remove_fn `views::remove`\endlink
-
Given a source range and a value, filter out those elements that do not equal value.
-
\link ranges::views::remove_if_fn `views::remove_if`\endlink
-
Given a source range and a unary predicate, filter out those elements that do not satisfy the predicate. (For users of Boost.Range, this is like the `filter` adaptor with the predicate negated.)
-
\link ranges::views::repeat_fn `views::repeat`\endlink
-
Given a value, create a range that is that value repeated infinitely.
-
\link ranges::views::repeat_n_fn `views::repeat_n`\endlink
-
Given a value and a count, create a range that is that value repeated *count* number of times.
-
\link ranges::views::replace_fn `views::replace`\endlink
-
Given a source range, a source value and a target value, create a new range where all elements equal to the source value are replaced with the target value.
-
\link ranges::views::replace_if_fn `views::replace_if`\endlink
-
Given a source range, a unary predicate and a target value, create a new range where all elements that satisfy the predicate are replaced with the target value.
-
\link ranges::views::reverse_fn `views::reverse`\endlink
-
Create a new range that traverses the source range in reverse order.
-
\link ranges::views::sample_fn `views::sample`\endlink
-
Returns a random sample of a range of length `size(range)`.
-
\link ranges::views::single_fn `views::single`\endlink
-
Given a value, create a range with exactly one element.
-
\link ranges::views::slice_fn `views::slice`\endlink
-
Give a source range a lower bound (inclusive) and an upper bound (exclusive), create a new range that begins and ends at the specified offsets. Both the begin and the end can be integers relative to the front, or relative to the end with "`end-2`" syntax.
-
\link ranges::views::sliding_fn `views::sliding`\endlink
-
Given a range and a count `n`, place a window over the first `n` elements of the underlying range. Return the contents of that window as the first element of the adapted range, then slide the window forward one element at a time until hitting the end of the underlying range.
-
\link ranges::views::split_fn `views::split`\endlink
-
Given a source range and a delimiter specifier, split the source range into a range of ranges using the delimiter specifier to find the boundaries. The delimiter specifier can be an element or a range of elements. The elements matching the delimiter are excluded from the resulting range of ranges.
-
\link ranges::views::split_when_fn `views::split_when`\endlink
-
Given a source range and a delimiter specifier, split the source range into a range of ranges using the delimiter specifier to find the boundaries. The delimiter specifier can be a predicate or a function. The predicate should take a single argument of the range's reference type and return `true` if and only if the element is part of a delimiter. The function should accept an iterator and sentinel indicating the current position and end of the source range and return `std::make_pair(true, iterator_past_the_delimiter)` if the current position is a boundary; otherwise `std::make_pair(false, ignored_iterator_value)`. The elements matching the delimiter are excluded from the resulting range of ranges.
-
\link ranges::views::stride_fn `views::stride`\endlink
-
Given a source range and an integral stride value, return a range consisting of every *N*th element, starting with the first.
-
\link ranges::views::tail_fn `views::tail`\endlink
-
Given a source range, return a new range without the first element. The range must have at least one element.
-
\link ranges::views::take_fn `views::take`\endlink
-
Given a source range and an integral count, return a range consisting of the first *count* elements from the source range, or the complete range if it has fewer elements. (The result of `views::take` is not a `sized_range`.)
-
\link ranges::views::take_exactly_fn `views::take_exactly`\endlink
-
Given a source range and an integral count, return a range consisting of the first *count* elements from the source range. The source range must have at least that many elements. (The result of `views::take_exactly` is a `sized_range`.)
-
\link ranges::views::take_last_fn `views::take_last`\endlink
-
Given a source range and an integral count, return a range consisting of the last *count* elements from the source range. The source range must be a `sized_range`. If the source range does not have at least *count* elements, the full range is returned.
-
\link ranges::views::take_while_fn `views::take_while`\endlink
-
Given a source range and a unary predicate, return a new range consisting of the elements from the front that satisfy the predicate.
-
\link ranges::views::tokenize_fn `views::tokenize`\endlink
-
Given a source range and optionally a submatch specifier and a `std::regex_constants::match_flag_type`, return a `std::regex_token_iterator` to step through the regex submatches of the source range. The submatch specifier may be either a plain `int`, a `std::vector`, or a `std::initializer_list`.
-
\link ranges::views::transform_fn `views::transform`\endlink
-
Given a source range and a unary function, return a new range where each result element is the result of applying the unary function to a source element.
-
\link ranges::views::trim_fn `views::trim`\endlink
-
Given a source bidirectional range and a unary predicate, return a new range without the front and back elements that satisfy the predicate.
-
\link ranges::views::unbounded_fn `views::unbounded`\endlink
-
Given an iterator, return an infinite range that begins at that position.
-
\link ranges::views::unique_fn `views::unique`\endlink
-
Given a range, return a new range where all consecutive elements that compare equal save the first have been filtered out.
-
\link ranges::views::values_fn `views::values`\endlink
-
Given a range of `pair`s (like a `std::map`), return a new range consisting of just the second element of the `pair`.
-
\link ranges::views::zip_fn `views::zip`\endlink
-
Given *N* ranges, return a new range where *M*th element is the result of calling `make_tuple` on the *M*th elements of all *N* ranges.
-
\link ranges::views::zip_with_fn `views::zip_with`\endlink
-
Given *N* ranges and a *N*-ary function, return a new range where *M*th element is the result of calling the function on the *M*th elements of all *N* ranges.
-
- -\subsection tutorial-actions Actions - --------------------------------------------------------------- -When you want to mutate a container in-place, or forward it through a chain of -mutating operations, you can use actions. The following examples should make it -clear. - -Read data into a vector, sort it, and make it unique. - -~~~~~~~{.cpp} - extern std::vector read_data(); - using namespace ranges; - std::vector vi = read_data() | actions::sort | actions::unique; -~~~~~~~ - -Do the same to a `vector` that already contains some data: - -~~~~~~~{.cpp} - vi = std::move(vi) | actions::sort | actions::unique; -~~~~~~~ - -Mutate the container in-place: - -~~~~~~~{.cpp} - vi |= actions::sort | actions::unique; -~~~~~~~ - -Same as above, but with function-call syntax instead of pipe syntax: - -~~~~~~~{.cpp} - actions::unique(actions::sort(vi)); -~~~~~~~ - -### List of range actions - -Below is a list of the eager range combinators, or actions, that Range-v3 -provides, and a blurb about how each is intended to be used. - -
-
\link ranges::actions::drop_fn `actions::drop`\endlink
-
Removes the first `N` elements of the source range.
-
\link ranges::actions::drop_while_fn `actions::drop_while`\endlink
-
Removes the first elements of the source range that satisfy the unary predicate.
-
`actions::erase`
-
Removes all elements in the sub-range of the source (range version) or all elements after position.
-
`actions::insert`
-
Inserts all elements of the range into the source at position.
-
\link ranges::actions::join_fn `actions::join`\endlink
-
Flattens a range of ranges.
-
`actions::push_back`
-
Appends elements to the tail of the source.
-
`actions::push_front`
-
Appends elements before the head of the source.
-
\link ranges::actions::remove_if_fn `actions::remove_if`\endlink
-
Removes all elements from the source that satisfy the predicate.
-
\link ranges::actions::remove_fn `actions::remove`\endlink
-
Removes all elements from the source that are equal to value.
-
\link ranges::actions::reverse_fn `actions::reverse`\endlink
-
Reverses all the elements in the container.
-
\link ranges::actions::shuffle_fn `actions::shuffle`\endlink
-
Shuffles the source range.
-
\link ranges::actions::slice_fn `actions::slice`\endlink
-
Removes all elements from the source that are not part of the sub-range.
-
\link ranges::actions::sort_fn `actions::sort`\endlink
-
Sorts the source range (unstable).
-
\link ranges::actions::split_fn `actions::split`\endlink
-
Split a range into a sequence of subranges using a delimiter (a value, a sequence of values, a predicate, or a binary function returning a `pair`).
-
\link ranges::actions::stable_sort_fn `actions::stable_sort`\endlink
-
Sorts the source range (stable).
-
\link ranges::actions::stride_fn `actions::stride`\endlink
-
Removes all elements whose position does not match the stride.
-
\link ranges::actions::take_fn `actions::take`\endlink
-
Keeps the first `N`-th elements of the range, removes the rest.
-
\link ranges::actions::take_while_fn `actions::take_while`\endlink
-
Keeps the first elements that satisfy the predicate, removes the rest.
-
\link ranges::actions::transform_fn `actions::transform`\endlink
-
Replaces elements of the source with the result of the unary function.
-
\link ranges::actions::unique_fn `actions::unique`\endlink
-
Removes adjacent elements of the source that compare equal. If the source is sorted, removes all duplicate elements.
-
\link ranges::actions::unstable_remove_if_fn `actions::unstable_remove_if`\endlink
-
Much faster (each element remove has constant time complexity), unordered version of `remove_if`. Requires bidirectional container.
-
- - -\subsection tutorial-utilities Utilities - ----------------------------------------------- -Below we cover some utilities that range-v3 provides for creating your own -view adaptors and iterators. - -### Create Custom Views with view_facade - -Range-v3 provides a utility for easily creating your own range types, called -\link ranges::view_facade `ranges::view_facade`\endlink. The code below uses -`view_facade` to create a range that traverses a null-terminated string: - -~~~~~~~{.cpp} - #include - - // A range that iterates over all the characters in a - // null-terminated string. - class c_string_range - : public ranges::view_facade - { - friend ranges::range_access; - char const * sz_ = ""; - char const & read() const { return *sz_; } - bool equal(ranges::default_sentinel_t) const { return *sz_ == '\0'; } - void next() { ++sz_; } - public: - c_string_range() = default; - explicit c_string_range(char const *sz) : sz_(sz) - { - assert(sz != nullptr); - } - }; -~~~~~~~ - -The `view_facade` class generates an iterator and begin/end member functions -from the minimal interface provided by `c_string_range`. This is an example of a -very simple range for which it is not necessary to separate the range itself -from the thing that iterates the range. Future examples will show examples of -more sophisticated ranges. - -With `c_string_range`, you can now use algorithms to operate on null-terminated -strings, as below: - -~~~~~~~{.cpp} - #include - - int main() - { - c_string_range r("hello world"); - // Iterate over all the characters and print them out - ranges::for_each(r, [](char ch){ - std::cout << ch << ' '; - }); - // prints: h e l l o w o r l d - } -~~~~~~~ - -### Create Custom Views with view_adaptor - -Often, a new range type is most easily expressed by adapting an existing range -type. That's the case for many of the range views provided by the Range-v3 -library; for example, the `views::remove_if` and `views::transform` views. These -are rich types with many moving parts, but thanks to a helper class called -\link ranges::view_adaptor `ranges::view_adaptor`\endlink, they aren't hard -to write. - -Below in roughly 2 dozen lines of code is the `transform` view, which takes one -range and transforms all the elements with a unary function. - -~~~~~~~{.cpp} - #include - - // A class that adapts an existing range with a function - template - class transform_view - : public ranges::view_adaptor, Rng> - { - friend ranges::range_access; - ranges::semiregular_box_t fun_; // Make Fun model semiregular if it doesn't - class adaptor : public ranges::adaptor_base - { - ranges::semiregular_box_t fun_; - public: - adaptor() = default; - adaptor(ranges::semiregular_box_t const &fun) : fun_(fun) {} - // Here is where we apply Fun to the elements: - auto read(ranges::iterator_t it) const -> decltype(fun_(*it)) - { - return fun_(*it); - } - }; - adaptor begin_adaptor() const { return {fun_}; } - adaptor end_adaptor() const { return {fun_}; } - public: - transform_view() = default; - transform_view(Rng && rng, Fun fun) - : transform_view::view_adaptor{std::forward(rng)} - , fun_(std::move(fun)) - {} - }; - - template - transform_view transform(Rng && rng, Fun fun) - { - return {std::forward(rng), std::move(fun)}; - } -~~~~~~~ - -Range transformation is achieved by defining a nested `adaptor` class that -handles the transformation, and then defining `begin_adaptor` and `end_adaptor` -members that return adaptors for the begin iterator and the end sentinel, -respectively. The `adaptor` class has a `read` member that performs the -transformation. It is passed an iterator to the current element. Other members -are available for customization: `equal`, `next`, `prev`, `advance`, and -`distance_to`; but the transform adaptor accepts the defaults defined in -\link ranges::adaptor_base `ranges::adaptor_base`\endlink. - -With `transform_view`, we can print out the first 20 squares: - -~~~~~~~{.cpp} - int main() - { - auto squares = ::transform(views::ints(1), [](int i){return i*i;}); - for(int i : squares | views::take(20)) - std::cout << i << ' '; - std::cout << '\n'; - // prints 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400 - } -~~~~~~~ - -The `transform_view` defined above is an input range when it is wrapping an -input range, a forward range when it's wrapping a forward range, etc. That happens -because of smart defaults defined in the `adaptor_base` class that frees you -from having to deal with a host of niggly detail when implementing iterators. - -*(Note: the above `transform_view` always stores a copy of the function in the -sentinel. That is only necessary if the underlying range's sentinel type models -bidirectional_iterator. That's a finer point that you shouldn't worry about right -now.)* - -#### view_adaptor in details - -Each `view_adaptor` contains `base()` member in view and iterator. -`base()` - allow to access "adapted" range/iterator: - -~~~~~~~{.cpp} - std::vector vec; - auto list = vec | views::transfom([](int i){ return i+1; }); - - assert( vec.begin() == list.begin().base() ); - assert( vec.begin() == list.base().begin() ); -~~~~~~~ - -Like `basic_iterator`'s `cursor`, `view_adaptor`'s `adaptor` can contain mixin class too, -to inject things into the public interface of the iterator: - -~~~~~~~{.cpp} - class adaptor : public ranges::adaptor_base - { - template - struct mixin : base_mixin - { - // everything inside this class will be accessible from iterator - using base_mixin::base_mixin; - - auto& base_value() const - { - return *this->base(); - } - - int get_i() const - { - return this->get().i; - } - }; - - int i = 100; - }; -~~~~~~~ - -From within mixin you can call: - -* `get()` - to access adaptor internals -* `base()` - to access adaptable iterator - -Iterator/sentinel adaptor may "override" the following members: - -~~~~~~~{.cpp} - class adaptor : public ranges::adaptor_base - { - // !For begin_adaptor only! - template - constexpr auto begin(Rng &rng) - { - return ranges::begin(rng.base()); - } - - // !For end_adaptor only! - template - constexpr auto end(Rng &rng) - { - return ranges::end(rng.base()); - } - - template - bool equal(I const &this_iter, I const &that_iter) const - { - return this_iter == that_iter; - } - // or - template - bool equal(I const &this_iter, I const &that_iter, adaptor const &that_adapt) const - { - return - *this.some_value == that_adapt.some_value - && this_iter == that_iter; - } - - // !For end_adaptor only! - // Same as equal, but compare iterator with sentinel. - // Not used, if iterator same as sentinel, and both have the same adaptor. - template - constexpr bool empty(I const &it, S const &end) const - { - return it == end; - } - // or - template - constexpr bool empty(I const &it, S const &end, SA const &end_adapt) const - { - return - *this.some_value == end_adapt.some_value - && it == end; - } - - template - reference_t read(I const &it) - { - return *it; - } - - template - void next(I &it) - { - ++it; - } - - // !For bidirectional iterator only! - template - void prev(I &it) - { - --it; - } - - // !For random access iterator only! - template - void advance(I &it, difference_type_t n) - { - it += n; - } - - // !For "sized" iterators only! - template - difference_type_t distance_to(I const &this_iter, I const &that_iter) - { - return that_iter - this_iter; - } - // or - template - difference_type_t distance_to - (I const &this_iter, I const &that_iter, adaptor const &that_adapt) - { - return that_iter - this_iter; - } - } -~~~~~~~ - -As you can see, some "overrides" have effect only for `begin_adaptor` or -`end_adaptor`. In order to use full potential of adaptor, you need to have -separate adaptors for begin and end: - -~~~~~~~{.cpp} - struct adaptor : adaptor_base - { - int n = 0; - - void next(iterator_t& it) - { - ++n; - ++it; - } - }; - - struct sentinel_adaptor : adaptor_base - { - int stop_at; - bool empty(const iterator_t&, const adaptor& ia, const sentinel_t& s) const - { - return ia.n == stop_at; - } - }; - - adaptor begin_adaptor() const { return {}; } - sentinel_adaptor end_adaptor() const { return {100}; } -~~~~~~~ - -Sometimes, you can use the same adaptor for both `begin_adaptor` and `end_adaptor`: - -~~~~~~~{.cpp} - struct adaptor : adaptor_base - { - int n = 0; - void next(iterator_t& it) - { - ++n; - ++it; - } - - // pay attention, we use equal, not empty. empty() will never trigger. - template - bool equal(I const &this_iter, I const &that_iter, adaptor const &that_adapt) const - { - return *this.n == that_adapt.n; - } - }; - - adaptor begin_adaptor() const { return {}; } - adaptor end_adaptor() const { return {100}; } -~~~~~~~ - -Note that all the data you store in the adaptor will become part of the iterator. - -If you will not "override" `begin_adaptor()` or/and `end_adaptor()` in your view_adaptor, default ones will be used. - -### Create Custom Iterators with basic_iterator - -Here is an example of Range-v3 compatible random access proxy iterator. -The iterator returns a key/value pair, like the `zip` view. - -~~~~~~~{.cpp} - #include - #include - - using KeyIter = typename std::vector::iterator; - using ValueIter = typename std::vector::iterator; - - struct cursor - { - // basic_iterator derives from "mixin", if present, so it can be used - // to inject things into the public interface of the iterator - struct mixin; - - // This is for dereference operator. - using value_type = std::pair; - ranges::common_pair read() const - { - return { *key_iterator, *value_iterator }; - } - - bool equal(const cursor& other) const - { - return key_iterator == other.key_iterator; - } - - void next() - { - ++key_iterator; - ++value_iterator; - } - - // prev optional. Required for Bidirectional iterator - void prev() - { - --key_iterator; - --value_iterator; - } - - // advance and distance_to are optional. Required for random access iterator - void advance(std::ptrdiff_t n) - { - key_iterator += n; - value_iterator += n; - } - std::ptrdiff_t distance_to(const cursor& other) const - { - return other.key_iterator - this->key_iterator; - } - - cursor() = default; - cursor(KeyIter key_iterator, ValueIter value_iterator) - : key_iterator(key_iterator) - , value_iterator(value_iterator) - {} - - KeyIter key_iterator; - ValueIter value_iterator; - }; - - struct cursor::mixin : ranges::basic_mixin - { - using ranges::basic_mixin::basic_mixin; - - // It is necessary to expose constructor in this way - mixin(KeyIter key_iterator, ValueIter value_iterator) - : mixin{ cursor(key_iterator, value_iterator) } - {} - - KeyIter key_iterator() - { - return this->get().key_iterator; - } - ValueIter value_iterator() - { - return this->get().value_iterator; - } - }; - - using iterator = ranges::basic_iterator; - - void test() - { - std::vector keys = {1}; - std::vector values = {10}; - - iterator iter(keys.begin(), values.begin()); - ranges::common_pair pair = *iter; - Key& key = pair.first; - Value& value = pair.second; - - assert(&key == &keys[0]); - assert(&value == &values[0]); - - auto key_iter = iter.key_iterator(); - assert(key_iter == keys.begin()); - } -~~~~~~~ - -`read()` returns references. But the default for `value_type`, which is -`decay_t`, is `common_pair`. That is not correct -in our case. It should be `pair`, so we explicitly specify -`value_type`. - -`ranges::common_pair` has conversions: - -> `ranges::common_pair` ↔ `ranges::common_pair`. - -All `ranges::common_pair`s converts to their `std::pair` equivalents, also. - -For more information, see [http://wg21.link/P0186#basic-iterators-iterators.basic](http://wg21.link/P0186#basic-iterators-iterators.basic) - -\subsection tutorial-concepts Concept Checking - --------------------------------------------------------------------------------- -The Range-v3 library makes heavy use of concepts to constrain functions, control -overloading, and check type constraints at compile-time. It achieves this with -the help of a Concepts emulation layer that works on any standard-conforming -C++14 compiler. The library provides many useful concepts, both for the core -language and for iterators and ranges. You can use the concepts framework to -constrain your own code. - -For instance, if you would like to write a function that takes an -iterator/sentinel pair, you can write it like this: - -~~~~~~~{.cpp} - CPP_template(class Iter, class Sent, class Comp = /*...some_default..*/) - (requires sentinel_for) - void my_algorithm(Iter first, Sent last, Comp comp = Comp{}) - { - // ... - } -~~~~~~~ - -You can then add an overload that take a Range: - -~~~~~~~{.cpp} - CPP_template(class Rng, class Comp = /*...some_default..*/) - (requires range) - void my_algorithm(Rng && rng, Comp comp = Comp{}) - { - return my_algorithm(ranges::begin(rng), ranges::end(rng)); - } -~~~~~~~ - -With the type constraints expressed with the `CPP_template` macro, these -two overloads are guaranteed to not be ambiguous. When compiling with C++20 -concepts support, this uses real concept checks. On legacy compilers, it falls -back to using `std::enable_if`. - -\subsection tutorial-future Range-v3 and the Future - --------------------------------------------------------------------------------- -Range-v3 formed the basis for the -[Technical Specification on Ranges](https://www.iso.org/standard/70910.html), -which has since been merged into the working draft of C++20. - -In addition, a subset of range-v3's views are also a part of the C++20 working -draft, with more slated for eventual inclusion in future versions of C++. - -The actions, as well as various utilities, have not yet been reviewed by the -Committee, although the basic direction has already passed an initial review. - diff --git a/vendor/range-v3/range-v3/doc/layout.xml b/vendor/range-v3/range-v3/doc/layout.xml deleted file mode 100644 index 304bf831..00000000 --- a/vendor/range-v3/range-v3/doc/layout.xml +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vendor/range-v3/range-v3/doc/release_notes.md b/vendor/range-v3/range-v3/doc/release_notes.md deleted file mode 100644 index 5045a37d..00000000 --- a/vendor/range-v3/range-v3/doc/release_notes.md +++ /dev/null @@ -1,308 +0,0 @@ -Release Notes {#release_notes} -============= - -\section v0-9-1 Version 0.9.1 - -_Released:_ Sept 1, 2019. - -gcc-9.x portability fixes. - -\section v0-9-0 Version 0.9.0 "Std::ranger Things" - -_Released:_ Aug 26, 2019. - -Bring many interfaces into sync with the C++20 draft. - -* **NEW:** An improved concepts portability layer with macros that use C++20 - concepts when the compiler supports them. -* **NEW:** An improved directory structure that keeps disjoint parts of the - library -- iterators, ranges, algorithms, actions, views, functional - programming support, and general utilities -- physically separate. -* **NEW:** A `RANGES_DEEP_STL_INTEGRATION` configuration option that makes your - STL implementation default to structural conformance to infer iterator - category, as in C++20. Applies to libc++, libstdc++, and MSVC's Standard - Library. -* **NEW:** A `ranges::cpp20` namespace that contains all the functionality of - C++20's `std::ranges` namespace. -* All concept names have been given standard_case (renamed from PascalCase) and - have been harmonized with the C++20 draft. -* The following range access customization points no longer accept rvalue ranges - by default: - - `ranges::begin` - - `ranges::end` - - `ranges::rbegin` - - `ranges::rend` - - `ranges::cbegin` - - `ranges::cend` - - `ranges::crbegin` - - `ranges::crend` - - `ranges::data` - - `ranges::cdata` -* Iterators may specify an `iterator_concept` type alias in addition to - `iterator_category` -- either as a nested type or as a member of a - `std::iterator_traits` specialization -- to denote conformance to the C++20 - iterator concepts as distinct from the C++98 iterator requirements. - (See [P1037 "Deep Integration of the Ranges TS"](http://wg21.link/p1037) - for more information.) -* The `ranges::value_type` trait has been renamed to `readable_traits`. -* The `ranges::difference_type` trait has been renamed to `incrementable_traits`. -* The `ranges::iterator_category` trait has been deprecated. Specialize - `std::iterator_traits` to non-intrusively specify an iterator's category - and (optionally) concept. -* Rename the `ranges::view` namespace to `ranges::views` and `ranges::action` to - `ranges::actions` (with deprecated namespace aliases for migration). -* Rename `view::bounded` to `views::common`. -* Rename `unreachable` to `unreachable_sentinel_t`. -* Change `dangling` from a class template that wraps an iterator to a class that - acts as a placeholder for an iterator that would otherwise dangle. -* Implement C++20's `subrange` as a view that wraps an iterator/sentinel pair; - deprecate `iterator_range`. -* Deprecate implicit conversion from view types to containers; rename - `ranges::to_` to `ranges::to` and extend it to support converting a - range-of-ranges to a container-of-containers. -* Deprecate the `ranges::v3` inline versioning namespace. -* The following views have had minor changes to bring them into conformance with - the C++20 working draft: - - `join_view` - - `single_view` - - `empty_view` - - `split_view` - - `reverse_view` - - `all_view` - - `take_view` - - `iota_view` -

`iota_view`, in particular, is given a user-defined - `difference_type` that avoids integer overflow. -* New names for the iterator and range type aliases: - | Old Name | New Name | - |-------------------------------|-----------------------------| - | `value_type_t` | `iter_value_t` | - | `reference_t` | `iter_reference_t` | - | `difference_type_t` | `iter_difference_t` | - | `size_type_t` | _deprecated_ | - | `rvalue_reference_t` | `iter_rvalue_reference_t` | - | `range_value_type_t` | `range_value_t` | - | `range_difference_type_t` | `range_difference_t` | - | `range_size_type_t` | `range_size_t` | - -\section v0-5-0 Version 0.5.0 - -_Released:_ Apr 30, 2019. - -* **NEW:** MSVC support, from @CaseyCarter :tada: (See the docs for the list of supported compilers.) -* **NEW:** `view::enumerate`, from @MikeGitb -* **NEW:** `view::addressof`, from @tower120 -* **NEW:** `unstable_remove_if` algorithm and action, from @tower120 -* **NEW:** `adjacent_remove_if` algorithm and action, from @cjdb -* **NEW:** `ostream_joiner`, from @sv1990 -* `view::drop_while` and `view::take_while` get projection support, from @mrpi -* `view::filter` and `view::remove_if` get projection support, from @mrpi -* `view::unique` accepts optional comparison operator, from @tete17 -* `action::slice` supports sliding from the end, from @tete17 -* Support coroutines on MSVC, from @CaseyCarter -* Faster `view::generate_n`, from GitHub user @tower120 -* Improved aligned new detection for libc++ on iOS, from @mtak- -* Various CMake improvements, from @johelegp -* `view_adaptor` supports `basic_iterator`-style mixins, from @tower120 -* Fix `ranges::advance` for random-access iterators for `n==0`, from @tower120 -* Bugs fixed: [#755](https://github.com/ericniebler/range-v3/issues/755), [#759](https://github.com/ericniebler/range-v3/issues/759), [#942](https://github.com/ericniebler/range-v3/issues/942), [#946](https://github.com/ericniebler/range-v3/issues/946), [#952](https://github.com/ericniebler/range-v3/issues/952), [#975](https://github.com/ericniebler/range-v3/issues/975), [#978](https://github.com/ericniebler/range-v3/issues/978), [#986](https://github.com/ericniebler/range-v3/issues/986), [#996](https://github.com/ericniebler/range-v3/issues/996), [#1041](https://github.com/ericniebler/range-v3/issues/1041), [#1047](https://github.com/ericniebler/range-v3/issues/1047), [#1088](https://github.com/ericniebler/range-v3/issues/1088), [#1094](https://github.com/ericniebler/range-v3/issues/1094), [#1107](https://github.com/ericniebler/range-v3/issues/1107), [#1129](https://github.com/ericniebler/range-v3/issues/1129) - -\section v0-4-0 Version 0.4.0 - -_Released:_ Oct 18, 2018. - -- Minor interface-breaking changes: - * `single_view` returns by `const &` (see [#817](https://github.com/ericniebler/range-v3/issues/817)). - * `reverse_view` of a non-Sized, non-Bounded RandomAccess range (eg., a null-terminated string) no longer satisfies SizedRange. - * The `generate` and `generate_n` views now return the generated values by xvalue reference (`T &&`) to the value cached within the view (see [#905](https://github.com/ericniebler/range-v3/issues/905)). - * Views no longer prefer returning constant iterators when they can; some views have different constant and mutable iterators. -- Enhancements: - * Views can successfully adapt other views that have different constant and mutable iterators. - * The `single` and `empty` views are much closer to the versions as specified in [P0896](http://wg21.link/P0896). -- Bug fixes: - * "single_view should not copy the value" [#817](https://github.com/ericniebler/range-v3/issues/817). - * "Calling back() on strided range does not return the correct last value in range" [#901](https://github.com/ericniebler/range-v3/issues/901). - * "generate(foo) | take(n) calls foo n+1 times" [#819](https://github.com/ericniebler/range-v3/issues/819). - * "generate seems broken with move-only return types" [#905](https://github.com/ericniebler/range-v3/issues/905). - * "Unexpected behavior in generate with return by reference" [#807](https://github.com/ericniebler/range-v3/issues/807). - * "Inconsistent behaviour of ranges::distance with ranges::view::zip using infinite views." [#783](https://github.com/ericniebler/range-v3/issues/783). - * "Infinite loop when using ranges::view::cycle with an infinite range" [#780](https://github.com/ericniebler/range-v3/issues/780). - * "Composing ranges::view::cycle with ranges::view::slice" [#778](https://github.com/ericniebler/range-v3/issues/778). - * "cartesian_product view, now with moar bugs." [#919](https://github.com/ericniebler/range-v3/issues/919). - - -\section v0-3-7 Version 0.3.7 - -_Released:_ Sept 19, 2018. - -- Improved support for clang-cl (thanks to @CaseyCarter). -- Fix for `any_view` (see #869). -- Fix `iter_move` of a `ranges::reverse_iterator` (see #888). -- Fix `move_sentinel` comparisons (see #889). -- Avoid ambiguity created by `boost::advance` and `std::advance` (see #893). - -\section v0-3-6 Version 0.3.6 - -_Released:_ May 15, 2018. - -- NEW: `view::exclusive_scan` (thanks to GitHub user @mitsutaka-takeda). -- All views get non-`const` overloads of `.empty()` and `.size()` (see [ericniebler/stl2\#793](https://github.com/ericniebler/stl2/issues/793)). -- Upgrade Conan support for conan 1.0. -- `subspan` interface tweaks. -- Fix bug in `view::split` (see [this stackoverflow question](https://stackoverflow.com/questions/49015671)). -- Fix bug in `view::stride` (see [ericniebler/stl2\#805](https://github.com/ericniebler/stl2/issues/805)). -- Fix `const`-correctness problem in `view::chunk` (see [this stackoverflow question](https://stackoverflow.com/questions/49210190)). -- Replace uses of `ranges::result_of` with `ranges::invoke_result`. -- Fix potential buffer overrun of `view::drop` over RandomAccessRanges. -- Lots of `view::cartesian_product` fixes (see [ericniebler/stl2\#820](https://github.com/ericniebler/stl2/issues/820), [ericniebler/stl2\#823](https://github.com/ericniebler/stl2/issues/823)). -- Work around gcc-8 regression regarding `volatile` `std::initializer_list`s (see [ericniebler/stl2\#826](https://github.com/ericniebler/stl2/issues/826)). -- Fix `const`-correctness problem of `view::take`. - -\section v0-3-5 Version 0.3.5 - -_Released:_ February 17, 2018. - -- Rvalues may satisfy `Writable` (see [ericniebler/stl2\#387](https://github.com/ericniebler/stl2/issues/387)). -- `view_interface` gets a bounds-checking `at` method. -- `chunk_view` works on Input ranges. -- Fix bug in `group_by_view`. -- Improved concept checks for `partial_sum` numeric algorithm. -- Define `ContiguousIterator` concept and `contiguous_iterator_tag` iterator - category tag. -- Sundry `span` fixes. -- `action::insert` avoids interfering with `vector`'s exponentional growth - strategy. -- Add an experimental `shared` view for views that need container-like scratch - space to do their work. -- Faster, simpler `reverse_view`. -- Rework `ranges::reference_wrapper` to avoid [LWG\#2993](https://wg21.link/lwg2993). -- Reworked `any_view`, the type-erased view wrapper. -- `equal` algorithm is `constexpr` in C++14. -- `stride_view` no longer needs an `atomic` data member. -- `const`-correct `drop_view`. -- `adjacent_filter_view` supports bidirectional iteration. -- Massive `view_adaptor` cleanup to remove the need for a `mutable` data - member holding the adapted view. -- Fix `counting_iterator` post-increment bug. -- `tail_view` of an empty range is an empty range, not undefined behavior. -- Various portability fixes for gcc and clang trunk. - -\section v0-3-0 Version 0.3.0 - -_Released:_ June 30, 2017. - -- Input views may now be move-only (from @CaseyCarter) -- Input `any_view`s are now *much* more efficient (from @CaseyCarter) -- Better support for systems lacking a working `` header (from @CaseyCarter) - -\section v0-2-6 Version 0.2.6 - -_Released:_ June 21, 2017. - -- Experimental coroutines with `ranges::experimental::generator` (from @CaseyCarter) -- `ranges::optional` now behaves like `std::optional` (from @CaseyCarter) -- Extensive bug fixes with Input ranges (from @CaseyCarter) - -\section v0-2-5 Version 0.2.5 - -_Released:_ May 16, 2017. - -- `view::chunk` works on Input ranges (from @CaseyCarter) -- `for_each_n` algorithm (from @khlebnikov) -- Portability fixes for MinGW, clang-3.6 and -3.7, and gcc-7; and cmake 3.0 - -\section v0-2-4 Version 0.2.4 - -_Released:_ April 12, 2017. - -Fix the following bug: -- `action::stable_sort` of `vector` broken on Clang 3.8.1 since ~last Xmas (ericniebler/range-v3#632). - -\section v0-2-3 Version 0.2.3 - -_Released:_ April 4, 2017. - -Fix the following bug: -- iterators that return move-only types by value do not satisfy Readable (ericniebler/stl2#399). - -\section v0-2-2 Version 0.2.2 - -_Released:_ March 30, 2017. - -New in this release: -- `view::linear_distribute(from,to,n)` - A view of `n` elements between `from` and `to`, distributed evenly. -- `view::indices(n)` - A view of the indices `[0,1,2...n-1]`. -- `view::closed_indices(n)` - A view of the indices `[0,1,2...n]`. - -This release deprecates `view::ints(n)` as confusing to new users. - -\section v0-2-1 Version 0.2.1 - -_Released:_ March 22, 2017. - -New in this release: -- `view::cartesian_product` -- `action::reverse` - -\section v0-2-0 Version 0.2.0 - -_Released:_ March 13, 2017. - -Bring many interfaces into sync with the Ranges TS. -- Many interfaces are simply renamed. The following table shows the old names - and the new. (All names are in the `ranges::v3` namespace.) - | Old Name | New Name | - |-------------------------------|---------------------------| - | `indirect_swap` | `iter_swap` | - | `indirect_move` | `iter_move` | - | `iterator_value_t` | `value_type_t` | - | `iterator_reference_t` | `reference_t` | - | `iterator_difference_t` | `difference_type_t` | - | `iterator_size_t` | `size_type_t` | - | `iterator_rvalue_reference_t` | `rvalue_reference_t` | - | `iterator_common_reference_t` | `iter_common_reference_t` | - | `range_value_t` | `range_value_type_t` | - | `range_difference_t` | `range_difference_type_t` | - | `range_size_t` | `range_size_type_t` | - | `range_iterator_t` | `iterator_t` | - | `range_sentinel_t` | `sentinel_t` | -- `common_iterator` now requires that its two types (`Iterator` and `Sentinel`) - are different. Use `common_iterator_t` to get the old behavior (i.e., if the two types are the same, it is an alias for `I`; otherwise, it is - `common_iterator`). -- The following iterator adaptors now work with iterators that return proxies - from their postfix increment operator (i.e., `operator++(int)`): - * `common_iterator` - * `counted_iterator` -- The following customization points are now implemented per the Ranges TS - spec and will no longer find the associated unconstrained overload in - namespace `std::`: - * `ranges::begin` - * `ranges::end` - * `ranges::size` - * `ranges::swap` - * `ranges::iter_swap` - - (In practice, this has very little effect but it may effect overloading in - rare situations.) -- `ranges::is_swappable` now only takes one template parameter. The new - `ranges::is_swappable_with` tests whether `T` and `U` are swappable. - `ranges::is_swappable` is equivalent to `ranges::is_swappable_with`. -- The following object concepts have changed to conform with the Ranges TS - specification, and approved changes (see [P0547](http://wg21.link/p0547)): - * `Destructible` - * `Constructible` - * `DefaultConstructible` - * `MoveConstructible` - * `MoveConstructible` - * `Movable` - * `Assignable` -- The `View` concept is no longer satisfied by reference types. -- The syntax for defining a concept has changed slightly. See [iterator/concepts.hpp](https://github.com/ericniebler/range-v3/blob/master/include/range/v3/iterator/concepts.hpp) for examples. - -\section v0-1-1 Version 0.1.1 - -- Small tweak to `Writable` concept to fix #537. - -\section v0-1-0 Version 0.1.0 - -- March 8, 2017, Begin semantic versioning diff --git a/vendor/range-v3/range-v3/doc/std/D4128.md b/vendor/range-v3/range-v3/doc/std/D4128.md deleted file mode 100644 index f3ca4568..00000000 --- a/vendor/range-v3/range-v3/doc/std/D4128.md +++ /dev/null @@ -1,2031 +0,0 @@ ---- -pagetitle: Ranges for the Standard Library, Revision 1 -title: Ranges for the Standard Library, Revision 1 -... - -Introduction -===== - -This paper outlines what support for ranges in the C++ standard library might look like. Rather than presenting a final design, this paper proposes a set of concepts and guidelines for using them to implement range-based versions of the standard algorithms. It draws inspiration from the [Boost.Range][2][@boostrange] library, the range algorithms in [Adobe Source Libraries][3][@asl], *Elements of Programming* by Stepanov and McJones (2009) [@stepanov09], and from [N3351 "A Concept Design for the STL"][8] by Stroustrup and Sutton (2012) [@n3351]. In addition to presenting the concepts and guidelines, this paper discusses the rationale behind each, weighing different design options. - -The decision to defer any discussion about specific wording was taken in recognition of the fact that any range design is likely to undergo significant revision by the committee. The paper is intended merely as a starting point for discussion and as a basis for future work. - -This paper assumes the availability of Concepts Lite; however, everything suggested here has been implemented in C++11, where Concepts Lite has been simulated with the help of generalized SFINAE for expressions. - -Motivation and Scope -===== - -A *range* is an object that refers to a sequence of elements, conceptually similar to a pair of iterators. One prime motivation for ranges is to give users a simpler syntax for calling algorithms. Rather than this: - - std::vector v { /*...*/ }; - std::sort( v.begin(), v.end() ); - -Ranges would give us a pithier syntax: - - std::sort( v ); - -Allowing algorithms to take a single range object instead of separate begin and end iterators brings other benefits besides convenience. In particular: - - * It eliminates the possibility of mismatched iterators. - * It opens the door to *range adaptors* which lazily transform or filter their underlying sequence in interesting ways. - -Range adaptors are far more compelling than iterator adaptors due to the fact that only a single object, the range object, needs to be adapted; hence, adaptors can be easily chained to create lazy computational pipelines, as in the code below which sums the first 10 squares: - - int total = accumulate(view::iota(1) | - view::transform([](int x){return x*x;}) | - view::take(10), 0); - -The standard defines the term "range" in [iterator.requirements.general]: - -> [...] in general, a range `[i,j)` refers to the elements in the data structure starting with the element pointed to by `i` and up to but not including the element pointed to by `j`. Range `[i,j)` is valid if and only if `j` is reachable from `i`. - -From the perspective of the standard library, a range *is* a pair of iterators. But there are other interesting ways to denote a range of elements: - -* An iterator and a count of elements -* An iterator and a (possibly stateful) predicate that indicates when the range is exhausted. - -One of these three range types can be used to denote all other range types, like an iterator and a sentinel value (e.g. a null-terminated string), or a range that spans disjoint ranges. Ideally, we would like our Range abstraction to be general enough to accommodate the three different kinds of ranges since that would increase the applicability of the algorithms. - -## Impact on the Standard - -Although this paper does not offer specific wording for any additions to the standard, we imagine that proper support for ranges in C++ would involve changes to the following parts of the standard: - -- New library-wide concepts related to ranges. -- New iterator algorithms for efficiently dealing with the new abstractions. -- Changes to existing algorithms to constrain the templates with concepts. -- Additional overloads of existing algorithms that accept ranges instead of pairs of iterators. -- Changes to the containers to allow containers to be constructed and assigned from ranges, and to allow range-based insert operations. -- A new library section for range adaptors, which are views of existing data that have been transformed or filtered and that compose with other views. -- General utilities for the construction of custom range adaptors. -- A minor change to the specification of the range-based `for` to make it more efficient and general. - -Future papers will make specific recommendations for all of the above, modulo any feedback on the design presented here. - -Proposed Design -===== - -The design space for ranges is surprisingly large. At one end of the spectrum lies [Boost.Range][2][@boostrange] and [Adobe's ASL][3][@asl] in which ranges are a thin abstraction on top of iterators, which remain the primitives that glue together data structures and algorithms. At the other end of the spectrum we find the [D Standard Library's std.range module][4][@drange], in which ranges and operations on them are the primitives themselves. - -This proposal picks a single point in this design space, and here we present the decisions that led to the selection of that point, along with guidelines to be applied to the standard library and the rationale for each choice. - -## Design Goals - -We feel that a well-designed range abstraction would: - -* Allow algorithms to operate on the three kinds of ranges with low or no abstraction penalty and a minimum of syntactic noise, -* Allow range-based algorithms to share implementation with iterator-based algorithms, -* Make it easy for users to reason about the complexity and expense of range operations (e.g. How many passes over the data are made? Are the elements copied? etc.), -* Protect the user from lifetime issues, -* Make it straightforward for users to make their types model one of the range concepts. - -It is helpful at this point to reflect on the success of C++11's range-based `for` loop. It succeeds because most of the types over which one would want to iterate already define iterators and `begin`/`end` members. Cleanly and efficiently interoperating with and reusing the existing abstractions of the STL is critical to the success of any range extensions. - -## High-Level Design - -At the highest level, this paper proposes the addition of two related range concepts: Iterable and Range. An *Iterable* type is one for which we can call `begin()` and `end()` to yield an iterator/sentinel pair. (Sentinels are described below.) The Iterable concept says nothing about the type's constructibility or assignability. Range-based standard algorithms are constrained using the Iterable concept. Consider: - - int buf[5000]; - // Fill buf - std::sort( buf ); - -`buf` denotes a random access range of elements, so we should be able to sort it; but native arrays are neither copyable nor assignable, so these operations should not be required by whatever range-like concept is used to constrain `sort`. The above line of code is equivalent to: - - using std::begin; - using std::end; - std::sort( begin( buf ), end( buf ) ); - -For an Iterable object `o`, the concept requires the following: - - auto b = begin( o ); // b models Iterator - auto e = end( o ); // e models Regular - bool f = (b == e); // b and e model EqualityComparable - -Algorithms will typically be implemented to take iterator/sentinel pairs, rather than the iterator/iterator pairs as they do now. A typical algorithm might look like: - - template - requires EqualityComparable - I some_algo(I first, S last, /*...*/) - { - for(; first != last; ++first) - /*...*/ - return first; - } - - template - IteratorOf some_algo( R & r, /*...*/ ) - { - return some_algo( begin(r), end(r), /*...*/ ); - } - -The *Range* concept is modeled by lightweight objects that denote a range of elements they do not own. A pair of iterators can be a model of Range, whereas a `vector` is not. Range, as opposed to Iterable, requires copyability and assignability. Copying and assignment are required to execute in constant time; that is, the cost of these operations is not proportional to the number of elements in the Range. - -The Range concept refines the Iterable concept by additionally requiring following valid expressions for an object `o` of type `O`: - - // Constructible: - auto o1 = o; - auto o2 = std::move(o); - O o3; // default-constructed, singular - // Assignable: - o2 = o1; - o2 = std::move(o1); - // Destructible - o.~O(); - -The Range concept exists to give the range adaptors consistent and predictable semantics, and memory and performance characteristics. Since adaptors allow the composition of range objects, those objects must be efficiently copyable (or at least movable). The result of adapting a Range is a Range. The result of adapting a container is also a Range; the container -- or any Iterable that is not already a Range -- is first converted to a Range automatically by taking the container's `begin` and `end`. - -The precise definitions of the suggested concepts are given in [Section 4](#concept-definitions), along with other supporting concepts that have proven useful while porting the algorithms. - -The use of sentinels instead of iterators as an Iterable's bound is best understood by seeing how the three different kinds of ranges can be made to model the Iterable concept. Below is a sketch of how `begin` and `end` might work for each kind. - -- **Pair of iterators**: An end iterator is a perfectly acceptable sentinel. Existing code that uses iterator pairs to call STL algorithms will continue working with no changes. -- **Iterator and predicate**: `begin(rng)` can return a normal iterator, `first`. `end(rng)` can return a sentinel `last` such that `first == last` returns the result of calling `last.predicate_(*first)`. See [Appendix 1](#appendix-1-sentinels-and-code-generation) for a discussion about the code generation benefits of letting the sentinel have a different type than the iterator. -- **Iterator and count**: `begin(rng)` can return an iterator `first` that bundles the underlying iterator with the count to the end. Incrementing the iterator decrements the count. `end(rng)` can return an empty sentinel `last` such that `first == last` returns the result of `first.count_ == 0`. See [Appendix 4](#appendix-4-on-counted-ranges-and-efficiency) for a discussion of the performance implications of this design. - -## Design Decisions, Guidelines, and Rationale - -Below we present the decisions that led to the chosen high-level design, along with guidelines to be applied to the standard library and the rationale for each choice. - -### Iterator Operations are Primitive - -The most fundamental decision facing the designer of a generic library like the STL is: what are the *basis operations*? Basis operations are the primitive operations upon which all other desired operations can be efficiently built. In the STL, those operations are the operations on iterators, and they are clustered into the familiar iterator concept hierarchy. Two iterators are required to denote a range. Can these two positions be bundled together into a single range object and -- more ambitiously -- can operations on ranges be made the basis, obviating the need for iterators entirely? - -Below we describe two libraries that use range operations as the basis, and describe why they are not a good fit for the C++ Standard Library. - -#### D's Ranges - -In the C++ Standard Library, iterators fill several roles: - -* Two of them denote a sequence of elements. -* One of them denotes a position within a range. -* They allow access to an element at the current position. -* They allow access to subsequent (and sometimes prior) positions in the sequence. - -The [D Standard Library][4][@drange] takes a different approach. In D, ranges and the operations on them form the basis operations of the standard algorithms. D-style ranges fill the following roles: - -* They denote a sequence of elements. -* They allow access to the front of the range, and sometimes to the back or the N-th. -* They allow the removal of elements from the front of the range, and sometimes from the back. - -Would C++ benefit from a similar design? The argument typically given in favor of D's ranges is that they lead to simpler code, both for the algorithms that operate on ranges as well as for users who wish to create custom range types. Code that manipulates positions directly can be harder to reason about and thus more bug-prone, and implementing custom iterators is famously complicated. - -D-style ranges can only ever shrink, and they have no notion of position within sequence. If one were to try to implement C++'s iterators on top of D's ranges, one would immediately run into trouble implementing ForwardIterator's `operator==`. As D's ranges do not represent position, there would be no way to test two ranges to see if their heads referred to the same element. (The `front` member function that returns the front of a D range is not required to return a reference, nor would that be sufficient to implement a hypothetical `hasSameFront` function; a repeating range might return the same element multiple times, leading to false positives.) Additionally, there would be trouble implementing BidirectionalIterator's `operator--` or RandomAccessIterator's `operator+=` as that might require a range to grow, and D ranges can't grow. - -On the other hand, two C++ iterators can easily be used to implement a D-style range; thus, every range-based design can be implemented in terms of iterators. Since iterators can implement D ranges, but D ranges cannot be used to implement iterators, we conclude that iterators form a more powerful and foundational basis. - -D avoids the limits of its ranges by carefully designing the algorithms such that the missing functionality is never needed. This can sometimes require some creativity, and leads to some awkward productions. A good example is the `find` family of algorithms. Since `find` cannot return the position of the found element, it must instead return a range. But which range to return? The D Standard Library has as many `find` algorithms as there are answers to this question ([`find`](http://dlang.org/phobos/std_algorithm.html#find), [`findSkip`](http://dlang.org/phobos/std_algorithm.html#.findSkip), [`findSplit`](http://dlang.org/phobos/std_algorithm.html#.findSplit), [`findSplitBefore`](http://dlang.org/phobos/std_algorithm.html#.findSplitBefore), [`findSplitAfter`](http://dlang.org/phobos/std_algorithm.html#.findSplitAfter)). All these algorithms find an element in a range; they only differ in the information they return. In contrast, C++ has just one `find` algorithm; it returns a position, and the user is free to construct any desired range from that. - -[Appendix 3](#appendix-3-d-ranges-and-algorithmic-complexity) contains an example of an algorithm that cannot be implemented with the same algorithmic complexity using D-style ranges. Any algorithm that needs to freely move an iterator forward and backward between two bounds will suffer from the same fundamental problem. Just writing the signature of an algorithm like `rotate`, which takes as an argument a position in a sequence, is challenging without a way to specify a position. - -Since C++ iterators cannot be implemented on top of D's ranges, iterators have to stay both for the increased expressive power and for backwards compatibility. To additionally provide D-style ranges -- essentially offering an incompatible set of basis operations -- would create a schism within the standard library between range-based and iterator-based algorithms, which couldn't share code. We, the authors, consider such a schism unacceptable. - -#### Position-Based Ranges - -If a range-first design that abandons "position" as a representable entity is undesirable for C++, perhaps adding position back in yields a satisfactory design. That is the approach taken by [James Touton's range library][5][@bekennrange], where ranges -- together with a new Position concept -- are the primitives. A Position, as its name suggests, represents a position in a range. Unlike an iterator, a position cannot be used to access the element at that position without the range into which it refers, nor can the position be advanced without the range. - -This design has the following advantages: - -* In making position a representable entity, it avoids the sometimes awkward constructions of D's range library. -* In requiring the range in order to dereference the position, it avoids all dangling iterator issues. -* In requiring the range in order to change the position, it makes range-checking trivial. This is a boon not just for debuggability, but also for the design of certain range adaptors like filter and stride whose iterators need to know the end of the range so as not to walk past it. -* It permits a clean separation of element traversal and access, much like the suggested [cursor/property map abstraction][11][@n1873]. - -It is possible to implement iterators on top of a position-based range by bundling a position with a pointer to a range into an iterator. However that results in iterators that may be fatter than necessary. - -A more workable approach would be to reimplement all the algorithms in terms of the position-based range primitives, and have the iterator-based overloads (that are retained for backwards-compatibility) forward to the range-based versions. In such a scheme, two iterators can be turned into a position-based range by wrapping them in a single range object and using the iterators themselves as the "positions". - -Although workable in principle, in practice it means there will be two ways of representing a range: an object with `begin()` and `end()` members that return iterators, and one with `begin_pos()` and `end_pos()` members that return positions; and every function that accepts ranges will need to account for that. It would mean that everybody would need to learn a new way to write algorithms. And it would mean that sometimes algorithms would return iterators and sometimes they would return positions, and that users would need to learn a new way to access the elements of a range. - -As appealing as the design is, it is too disruptive a change for the Standard Library. - -#### Basis-Operations: Summary - -In short, it just doesn't seem worth the trouble to change basis-operation horses in midstream. Iterators have a proven track record as a solid basis for the algorithms, and the C++ community has a heavy investment in the abstraction. The most heavily used and vetted C++ range libraries, Boost.Range and ASL Ranges, are built on top of iterators and have shown that it works in practice. This proposal follows suit. - -### Ranges Cannot Own Elements - -As described above, a Container is not a Range; it is, however, an Iterable. Distinguishing between the two makes it possible to be explicit about where copyability is required, and with what performance characteristics. - -The algorithms discussed in this proposal don't require any distinction between Ranges and Containers since they never copy or assign the ranges passed to them; they only request the `begin` and `end` iterators. The distinction between Iterables and Ranges only becomes important when defining adaptor chains. What does code like the following mean? - - auto rng = v | view::reverse; - -This creates a view of `v` that iterates in reverse order. Now: is `rng` copyable, and if so, how expensive is the copy operation? If `v` is a `vector`, can `rng` safely outlive `v`? How about if `v` is just a pair of iterators? What happens when a user does `*rng.begin() = 42`? Is `v` mutated? How do the answers change if we replaced `v` with an rvalue expression? If a copy of `rng` is made, and an element is mutated through the copy, does the original `rng` object "see" the change? - -By specifying that Ranges do *not* own their elements, and further specifying that range adaptors operate on and produce Ranges (not Containers), we are able to answer these questions in a clear and consistent way. The result of a chain of range adaptors is always a lightweight object that is cheap to copy and assign (O(1) as opposed to O(N)), and that refers to elements whose lifetime is managed by some other object. Mutating an element through the resulting Range object mutates the underlying sequence. Copies of the resulting range are aliases to the same elements, and mutations to the elements are visible through all the aliased ranges. - -If `v` is a Range in the above line of code, it is copied into the adaptor. If it is a `vector`, the `vector` is first used to construct a Range by taking the `begin` and `end` of the Container. This happens automatically. - -The downside of this design is that it is sometimes desirable to do this: - - // Try to adapt an rvalue container - auto rng = vector{1,2,3,4} | view::reverse; // OK? - -Adaptors operate on and yield Ranges; other Iterables (i.e., containers) are used to construct Ranges by first taking their begin and end. The code above is unsafe because `rng` will be left holding invalid iterators into a container that no longer exists. Our solution is to disallow the above code. *It is illegal to adapt an rvalue non-Range.* (Adapting rvalue Ranges, however, is perfectly acceptable; indeed necessary if adaptor pipelines are to work.) See [Appendix 6](#on-distinguishing-ranges-from-non-range-iterables) for the mechanics of how we distinguish between Iterables and Ranges. - -The alternative is for the rvalue container to be moved (or copied) into the adapted range and held by value. The resulting object would therefore no longer model Range; it would model Iterable. The authors feel that this weakening of the requirements on the return type makes it difficult to reason about the semantics and algorithmic complexity of range adaptors. The recommendation is to first declare the container and then create the adaptor separately. - -### Ranges Are Semiregular - -We've already decided that Ranges (not Iterables) are copyable and assignable. They are, in the terminology of EoP[@stepanov09] and [N3351][8][@n3351], Semiregular types. It follows that copies are independent, even though the copies are both aliases of the same underlying elements. The ranges are independent in the same way that a copy of a pointer or an iterator is independent from the original. Likewise, iterators from two ranges that are copies of each other are also independent. When the source range goes out of scope, it does not invalidate an iterator into the destination range. - -Semiregular also requires DefaultConstructible in [N3351][8]. We follow suit and require all Ranges to be DefaultConstructible. Although this complicates the implementation of some range types, it has proven useful in practice, so we have kept this requirement. - -It is tempting to make Ranges Regular by requiring that they be EqualityComparable. A Range type would satisfy this additional requirement by testing whether two Ranges refer to the same elements in a sequence. (Note that it would be an error for `operator==` to test corresponding elements in two Ranges for equality, in the same way that it would be an error for `operator==` invoked on two pointers to compare the pointed-to elements. The state of a Range is not influenced by the content of its elements; a Range is defined only by the identity of the elements it references.) Although such a requirement is appealing in theory, it has problems: - -* It might conflict with users' expectations of what `rng1 == rng2` means. (See string_view for an example of a Range-like class that implements `operator==` in terms of the values of its elements, rather than identity.) -* It is impossible to implement with those semantics in O(1) for some range types; for example, a filter range that stores a predicate. Functors and lambdas are generally not EqualityComparable. - -Another option is to allow Ranges to trivially model EqualityComparable by narrowly defining the domain over which the operation is valid. Iterators may only be compared if they refer into the same range. We can extend the reasoning to Ranges, which are logically little more than pairs of iterators. Taking this tack, we could allow a Range type to define its `operator==` as: - - rng1.begin() == rng2.begin() && rng1.end() == rng2.end() - -The assumption being that the operation is invalid if `rng1` and `rng2` refer to different elements. Although principled (for some set of principles), such a definition is almost certain to lead users into undefined behavior-land. - -As a result of the above, we have decided that the Range concept should not require EqualityComparable. Ranges are Semiregular, not Regular. - -If a user would like to check to see if two ranges have elements that compare equal, we suggest the `equal` algorithm: - - if(std::equal(rng1, rng2)) - // ... - -### Range Iterators Cannot Outlive Their Ranges - -Containers own their elements, so it is clear that the container must outlive the iterators it generates. But is the same true for a Range if it does not own its elements? For a trivial range consisting of two iterators, it's clearly not true; the iterators *may* outlive the range. - -It turns out that if we require that all range's iterators be permitted to outlive the range, a great many interesting range types become significantly more expensive at runtime. A good case study is the filter view. - -A filter view takes a range and a predicate, and presents a view of the sequence that skips the elements for which the predicate is false. (The filter view can be thought of as a lazy equivalent of the `copy_if` algorithm.) The existence of the `boost::filter_iterator` shows that such an iterator *can* be made such that it doesn't depend on a range, but at a cost. The `filter_iterator` stores: - - 1. An iterator that indicates the current position in the underlying sequence. - 2. An iterator that indicates the end of the underlying sequence (needed by the increment operators to avoid falling off the end while searching for an element that satisfies the predicate). - 3. The predicate. - -In today's STL, the begin and end iterators must have the same type, and they are both needed to call an algorithm. Thus, the information in (2) and (3) is duplicated. Also, the predicate may be expensive to copy, given the ease with which capture-by-value lambdas and `std::function`s can be created. When such iterators are composed with other kinds of views (e.g., a transformed, filtered view), the bloat compounds exponentially (see [Index-Based Ranges][10][@n3782]). - -By relaxing the constraint that a range's begin and end must have the same type, we can avoid the duplication, but the begin iterator still must hold everything, which is potentially expensive. - -If we could rely on the range object outliving the iterator, we can make the filter iterators smaller and lighter. The range object can hold the predicate and the underlying range's begin/end iterators. The filter view's iterator only needs to hold the current position and a pointer back to the range object. - -The same logic applies to the transform view, which applies a transformation function to elements on the fly. If the iterators are required to be valid beyond the lifetime of the transform range, then the transformation function must be cached inside each iterator. This could make the iterators expensive to copy. An implementor might instead want the freedom to put the transformation function in the range object. - -A final example is `istream_iterator`. Every `istream_iterator` holds a single cached `T` object inside it. If `T` is an expensive-to-copy type like `std::string`, then copying `istream_iterator`s is potentially causing dynamic allocations. The STL assumes iterators are cheap to copy and copies them freely. A better design would see the cached `string` object move into an `istream_range` object whose iterators merely stored pointers back to the range. This would make the iterators smaller and cheaper to copy, but they would become invalid once the range was destroyed. This tradeoff is probably worth it. - -### An Iterable's End May Have a Different Type Than Its Begin - -In today's STL, `c.begin()` must have the same type as `c.end()`. This is because the only kind of range the STL supports is a pair of iterators. However, we've given examples of other kinds of ranges we would like to support, such as an iterator and a predicate, and an iterator and a count. These kinds of ranges can already be supported by shoe-horning them into the pair-of-iterators mold, but at a cost (see [Appendix 1](#appendix-1-sentinels-and-code-generation)). Loosening the constraints of the type of Iterable's end makes it possible to accommodate these other kinds of ranges with lower overhead. - -Allowing "end-ness" to be encoded in the type system also eliminates the need to mock-up dummy end iterators like `std::istream_iterator` and `std::regex_iterator`, the logic of which is tricky to get right. What's more, it improves code generation for these kinds of ranges. With the use of dummy end iterators, information which is known at compile-time -- namely, that an iterator represents the end -- must be encoded into runtime information in the iterator itself. This robs the compiler of the information it needs to eliminate branches from the core loops of many algorithms. See [Appendix 1](#appendix-1-sentinels-and-code-generation) for an example of how sentinels can positively affect code generation. - -When considering this choice for the range concept, it's helpful to think about how it would affect the algorithms. Consider `std::for_each`, which currently has this signature: - - template - Function for_each(InputIterator first, InputIterator last, Function f) - { - for(; first != last; ++first) - f(*first); - return f; - } - -With sentinels, `for_each` might look like this: - - template> F> - requires EqualityComparable - F for_each(I first, S last, F f) - { - for(; first != last; ++first) - f(*first); - return f; - } - -None of the code in the algorithm had to change. No calling code would have to change either; this is a strictly backwards-compatible change. You might think that this opens a new category of programming errors where developers inadvertently pass mismatched iterator/sentinel pairs. However, this algorithm signature is constrained with concept checks that ensures that `I` and `S` satisfied the cross-type EqualityComparable concept (see [N3351][8][@n3351]). See [Appendix 2](#appendix-2-sentinels-iterators-and-the-cross-type-equalitycomparable-concept) for further discussion about iterator/sentinel cross-type EqualityComparability constraint. - -To see the benefit of this design, imagine a sentinel type `null_sentinel`: - - // For determining whether an iterator refers to a null value: - struct null_sentinel - { - template - friend bool operator==(I i, null_sentinel) { return 0 == *i; } - // ... and friends - }; - - template - struct common_type - ... see Appendix 2 ... - -Now we can use `std::for_each` on null-terminated strings without needing to know the length of the string: - - std::for_each(argv[1], null_sentinel(), f); - -Of course, all the algorithms would have overloads that also accept range arguments, so this can be further simplified to: - - std::for_each(null_terminated(argv[1]), f); - -where `null_terminated(InputIterator)` returns a range `r` such that the `std::end(r)` is a `null_sentinel`. - -#### Sentinels and Early Algorithm Termination - -One excuse sometimes given for not using the standard algorithm is that they don't give the users a way to break out of them early. The use of sentinels makes that possible. Consider a sentinel constructed from both an end iterator and a predicate. Such a sentinel would compare equal to an iterator *either* when the iterator equals the end iterator *or* when the predicate evaluates to true. Using such a sentinel has the effect of terminating an algorithm early. For instance: - - // Process work items in a queue, allowing for a user interrupt - std::queue q; - std::function user_interrupt = /*...*/; - std::for_each( q | view::until(user_interrupt), f ); - -In the above, `view::until` is a range modifier that adds `user_interrupt` as an extra termination condition. - -### Algorithm Return Types are Changed to Accommodate Sentinels - -Notice that in the due course of evaluating `std::for_each` with `null_sentinel` above, the position of the null terminator is found. This is potentially useful information that can easily be returned to the user. It is, in fact, a far more interesting and useful result that the `Function` that `for_each` currently returns. So a better signature for `for_each` should look like this: - - // Returns an InputIterator i such that (i == last) is true: - template> F> - requires EqualityComparable - I for_each(I first, S last, F f); - -In similar fashion, most algorithm get new return types when they are generalized to support sentinels. This is a source-breaking change in many cases. In some cases, like `for_each`, the change is unlikely to be very disruptive. In other cases it may be more so. Merely accepting the breakage is clearly not acceptable. We can imagine three ways to mitigate the problem: - -1. Only change the return type when the types of the iterator and the sentinel differ. This leads to a slightly more complicated interface that may confuse users. It also greatly complicates generic code, which would need metaprogramming logic just to use the result of calling some algorithms. For this reason, this possibility is not explored here. - -2. Make the new return type of the algorithms implicitly convertible to the old return type. Consider `copy`, which currently returns the ending position of the output iterator. When changed to accommodate sentinels, the return type would be changed to something like `pair`; that is, a pair of the input and output iterators. Instead of returning a `pair`, we could return a kind of pair that is implicitly convertible to its second argument. This avoids breakage in some, but not all, scenarios. This subterfuge is unlikely to go completely unnoticed. - -3. Deliver the new standard library in a separate namespace that users must opt into. In that case, no code is broken until the user explicitly ports their code. The user would have to accommodate the changed return types then. An automated upgrade tool similar to [clang modernize][16][@clangmodernize] can greatly help here. - -We, the authors, prefer (3). Our expectation is that the addition of concepts will occasion a rewrite of the STL to properly concept-ify it. The experience with C++0x Concepts taught us that baking concepts into the STL in a purely backward-compatible way is hard and leads to an unsatisfactory design with a proliferation of meaningless, purely syntactic concepts. The spirit of [N3351][8][@n3351] is to conceptify the STL in a meaningful way, even at the expense of minor breakage. Although the issue has not yet been discussed, one likely solution would be to deliver a new STL in a separate namespace. Once that happens, it opens the door for other minor breaking changes, provided the benefits are deemed worthy. - -### Orthogonality of Traversal and Access Is Not Surfaced in the Iterator Concepts - -The current iterator concept hierarchy ties together the traversal and access properties of iterators. For instance, no forward iterator may return an rvalue proxy when it is dereferenced; the ForwardIterator concept requires that unary `operator*` return an lvalue. There is no room in the hierarchy for, say, a random-access iterator that returns proxies. - -This problem is not new to ranges; however, it has serious consequences for lazy ranges that apply transformations to elements on the fly. If the transformation function does not return an lvalue, the range's iterator can model no concept stronger than InputIterator, even if the resulting iterator could in theory allow random access. The result in practice is that most range adaptors today have the unfortunate effect of degrading the underlying range's category to Input, thereby limiting the number of algorithms it can be passed to -- often for no good reason. - -Prior work has been done by [Abrahams et. al.][9][@new-iter-concepts] to separate the traversal and access properties of iterators in a way that is backwards compatible. When formalizing the iterator concepts for a range library, should new iterator concepts be used, or should we hew to the old, simpler concept hierarchy with its known limitations? - -A close reading of the iterator concepts proposed by [N3351][8][@n3351] shows that the problematic requirement on ForwardIterator's reference type has been dropped. That is, if the concepts proposed by Stroustrup, Sutton, et. al., are adopted, there will be nothing wrong with, say, a random-access iterator with a proxy for a reference type. The problems described above go away without any need for further complication of the iterator refinement hierarchy. It's unclear at this time if that was the authors' intent, and if so what impact it has on the standard algorithms. This area requires more research. - -It is noted here that this proposal adopts the [N3351][8][@n3351] iterator concepts as-is. - -### Additional Overloads of the Algorithms - -As should be obvious, this range proposal recommends adding additional overloads of the existing algorithms to allow them to work directly on Iterables. This is done in accordance with the following suggested guidelines: - -- Any algorithm that currently operates on a range denoted by two iterators gets an overload where the two iterator arguments are replaced with a single Iterable argument. (NOTE: This does *not* include the counted algorithms like `copy_n` that take an iterator and a count instead of two iterators.) -- All overloads of an algorithm, whether they take Iterables or separate iterator/sentinel arguments, are *semantically identical*. All overloads have the same return type. All evaluate eagerly. The intention is that the Iterable-based overloads delegate to the iterator-based ones and have the same semantics and algorithmic complexity. -- As described above, algorithms that necessarily process their entire input sequence return the iterator position at the end in addition to whatever else they return. The purpose is to return potentially useful information that is computed as a side-effect of the normal execution of the algorithm. Exceptions to this design guideline are made when one of the following is true: - * The algorithm might in some cases not consume the entire input sequence. (The point of this exception is to avoid forcing the algorithm to compute something that is not necessary for successful completion. For example, `find`.) - * When the sole purpose of the algorithm is specifically to compute a single value; hence, changing the return type will necessarily break code using the C++11 version. Examples include `is_sorted` and `accumulate`. -- "Three-legged" iterator-based algorithms (i.e. algorithms that operate on two ranges, the second of which is specified by only a single iterator and is assumed to be long enough) now have 4 versions: - 1. The old three-legged iterator version, - 2. A four-legged version that uses the sentinel of the second sequence as an additional termination condition, - 3. A version that takes an Iterable and an Iterator (which dispatches to the three-legged iterator-based version), - and - 4. A version that takes two Iterables (which dispatches to the four-legged iterator-based version). - - Note: Purely as an implementation consideration, overloads (3) and (4) above must be coded to avoid ambiguity when a native array is passed as the second parameter (where either an Iterable or an Iterator may appear). Arrays are Iterables, but if (3) is naively coded to take an Iterator by value, a native array would also match, since native arrays decay into pointers. -- If an algorithm returns an iterator into an Iterable argument, the Iterable must be an lvalue. This is to avoid - returning an iterator that is immediately made invalid. Conversely, if no iterator into an Iterable argument is - returned, then the Iterable should be taken by forwarding reference (aka ["universal reference"][13][@universal-references]). -- Algorithms that do not mutate their input sequence must also work when braced initializer lists [dcl.init] are used in place of Iterables. This can require additional `initializer_list` overloads since a type cannot be deduced from a *braced-init-list* used as an argument. - -### Range-based for Loop is Changed to Accommodate Sentinels - -The current range-based `for` loop assumes that a range's end has the same type as its begin. This restriction can be relaxed to allow range-based `for` to operate on Iterables. - -The range-based for loop is currently specified as: - - { - auto && __range = range-init; - for ( auto __begin = begin-expr, - __end = end-expr; - __begin != __end; - ++__begin ) { - for-range-declaration = *__begin; - statement - } - } - -To accommodate Iterables, the change is as simple as: - - { - auto && __range = range-init; - auto __begin = begin-expr; - auto __end = end-expr; - for ( ; __begin != __end; ++__begin ) { - for-range-declaration = *__begin; - statement - } - } - -This is the only core language change required to fully support Iterables. - -### Allow Mutable-Only Iterables - -If a cv-unqualified type `T` models Iterable, then the type `T const` need not. This permits ranges that maintain mutable internal state such as an `istream_range`. - -Consider the performance short-comings of `istream_iterator`. The iterator reads a string from an `istream` and must store it internally so it can be returned from `operator*`. This means that copying an `istream_iterator` probably incurs a dynamic allocation. Copying iterators is not supposed to be expensive. - -An alternative range-based design would be to define an `istream_range` class template: - - template - class istream_range { - T value_; - istream * postr_; - public: - class iterator { - istream_range * prng_; - public: - /*...*/ - }; - iterator begin() {/*...*/} - iterator end() {/*...*/} - }; - -In this design, the cached value lives in the range object, not in the iterator. The iterator merely stores a pointer back to the range. As the iterator is advanced, values are read from the stream and stored in the cache. Since the range object is mutated as it is iterated, it would be a lie to provide const overloads of `begin()` and `end()`. - -The need for a range that is mutated in the course of iteration also came up in the design of a `view::flatten` adaptor that takes a range of ranges, and turns it into a single range. This adapted range also must mutate an internal cache. The adaptor is used to make Ranges monads, and is used in the implementation of [Range Comprehensions][12][@range-comprehensions], which are akin to Python's and Haskell's List Comprehensions. (That discussion is beyond the scope of this document.) - -### Range Adaptors are Lazy Algorithms - -Consider the example given at the start of paper: - - int total = accumulate(view::iota(1) | - view::transform([](int x){return x*x;}) | - view::take(10), 0); - -The semantics of the adaptors (the things in the `view::` namespace) are such that computations are only done on demand, when the resulting adapted range is iterated. In this case, the range pipeline expression does no work aside from setting up a computation. Only as `accumulate` executes is the sequence of integers generated and transformed. - -All adaptors have these lazy semantics. This gives users the ability to reason about the algorithmic complexity of their programs. If an underlying range is transformed and filtered and then passed to an algorithm, users can be certain that the sequence will be traversed exactly once. - -### All Algorithms Accept Sentinels Even If They Need An End Iterator - -Some algorithms like `reverse` really need an end iterator to do their job. One option would be to require users to pass an actual end iterator instead of a non-iterator sentinel. The other option is to accept a sentinel and do an O(N) probe for the end first. We opted for the latter option. No such algorithms have complexity better than O(N), so such a probe doesn't affect the overall algorithmic complexity of the algorithm. And it saves the user the hassle of having to find the end herself before calling the algorithm. In short, it should be possible to `reverse` a null-terminated string *without* needing to call `strlen` first. - -### There Is No Function Signature To Express Does-Not-Mutate-The-Range-Elements - -Raw pointers have an advantage over iterators with regard to the const-correctness of function interfaces; namely, that you can use `const` to guarantee that the function will not mutate the pointed-to data. Consider: - - // This will not mutate the data - template - void some_function(const T * data, std::size_t count); - -When we move to iterators, we lose that expressiveness: - - // Will this mutate the data? - template - void some_function(Iter first, Iter last); - -Since the suggested range design is built on top of iterators, it inherits this limitation. Consider what happens when we change to a range-based API with a simple `range` type like that proposed by [N3350][21][@n3350]: - - // Accepts a const Range object, but mutates its elements - template - void some_function(Rng const & rng) { - ++*rng.begin(); // Deeply questionable behavior - } - - template - struct range : pair { - using pair::pair; - I begin() const { return this->first; } - I end() const { return this->second; } - }; - - int main() { - int arr[] = {1,2,3,4}; - range rng{ arr, arr + 3 }; - some_function(rng); // arr is now {2,2,3,4} - } - -Keep in mind that the Iterable concept describes types like `vector` where a top-level const *does* affect the mutability of the iterable's elements, as well as simple pair-of-iterator style ranges like `range` above, where the top-level constness does *not* affect the elements' mutability. Concepts Lite doesn't help, either. Although it's not hard to write a concept that only matches immutable iterables and use it to constrain an algorithm, it doesn't solve the problem. See below: - - template - concept ConstIterable = - is_const())) - >>::value; - - // This is over-constrained: - template - void non_mutating_algorithm(Rng const & rng); - - int rgi[] = {1,2,3,4}; - range rng{rgi, rgi+4}; // a pair of mutable iterators - non_mutating_algorithm(rng); // <== ERROR rng is not a ConstIterable - -The trouble is that the type `Rng` is deduced as `range` which doesn't satisfy the requirements of the ConstIterable concept. The const in the function signature doesn't help because it doesn't change the const-ness of the iterator's reference type. Even if `rng` is const, `rng.begin()` returns an `int*`. There is no way to write the function signature such that template type deduction works *and* const-ness is applied deeply to the range's elements. That's because such a transformation would necessarily involve a change to the range's type, and type deduction makes no allowance for arbitrary type transformations during the deduction process. - -#### But it works for `array_view`. Why? - -[N3851](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3851.pdf) proposes an `array_view` class for the second Library Fundamentals TS. That type allows users to independently specify the mutability of the elements and of the view with the `const` keyword. From N3851: - -> A view can be created over an arbitrary *value_type*, as long as there exists a conversion from the pointer to the underlying collection object type to the pointer to the *value_type*. This allows to distinguish two levels of constness, analogously to the pointer semantics -- the constness of the view and the constness of the data over which the view is defined -- see Table 2. Interfaces of both the *array_view* and the *strided_array_view* allow for implicit conversions from non-const-qualified views to const-qualified views. - -With this scheme, you presumably would be able to pass any Container (defined in N3851 to be arrays and class types with `size` and `data` member functions) to an algorithm as follows: - - void some_algorithm( array_view const & arr ); - - vector vi {1,2,3,4}; - array_view arr { vi } - some_algorithm( arr ); // OK! - -In this case, the signature of the algorithm, together with the constructors of `array_view` and the implicit conversion of `int*` to `const int*`, conspire to make `some_function` guarantee deep-constness. - -If it can work for `array_view`, why can it not work in general for all Iterables? In other words, is there no range design in which users can put `const` somewhere in their function signatures to make their algorithms deeply const? The answer, sadly, is no. Even a trivial change to the above signature shows how fragile the `array_view` design is, and why it cannot be generalized to all range types: - - template - void some_algorithm( array_view const & arr ); - - vector rgi{1,2,3,4}; - array_view arr { vi } - some_algorithm( arr ); // ERROR! - -The compiler cannot figure out what type to pick for `T` during type deduction, and there's no way for the author of `array_view` to give the compiler that guidance. - -When generalized to arbitrary Iterables and Ranges, the problem is further compounded. There is no place to put a second, "deep" `const` in a signature like below: - - template - void some_algorithm( Rng const & rng ); - -And even if there were, the problem would be the same as for `array_view`: template type deduction would fail. - -#### Constraints and contracts - -There is no clever way around the problem in today's language, or even with Concepts Lite. This is an artifact of the style of generic programming used in C++: stating constraints on template arguments is significantly different than adding `const`-ness to a concrete type or template specialization. - -A template's constraints define part of that template's contract, and this contract applies not only to the user of that template, but also its implementer. In the earlier example, `some_function` requires its template arguments to satisfy the requirements of the Iterable concept. Under that contract, the implementation should use only the expressions allowed by the concept on objects whose types are constrained by the concept. In other words, those requirements define a set of expressions and types that are *admissible* within the template. - -Recall the `some_function` define above: - - // Accepts a const Range object, but mutates its elements - template - void some_function(Rng const & rng) { - ++*rng.begin(); // Deeply questionable behavior - } - -The expression `++*rng.begin()` invokes not one, but two operations not admitted by the requirements of the Iterable concept: - -- `rng.begin()` (which should be `using std::begin; begin(rng)`), and -- `++x` where `x` is the object returned by `*begin(rng)`. - -While this program is syntactically well-formed, and instantiation may even succeed, its behavior would be undefined under the terms of its contract. In other words, the implementation does not conform to its constraints. This is not fundamentally different than using a `const_cast` to modify the value of a `const`-qualified function argument: it might work, but the modification invokes undefined behavior. - -*Every* proposed range design will have the same problem. The lack of a library solution to this problem should not hold up a proposal. A work-around is for users to test each algorithm with an archetypal non-mutable iterator, as has been done with the [Boost Concept Check Library][1][@boostconceptcheck]. The standard library could make this task simpler by providing debug archetypal iterators for testing purposes. - -### Singular Ranges Are Not Empty - -Ranges are default-constructible. The question is, what can be done with a such a range? Given a Range type `R` and an object `r` defined as follows: - - R r; // "singular" range - assert( begin(r) == end(r) ); // Is this OK? - -Default-constructed standard containers are empty. Can we expect the same is true for ranges? No, we cannot. The intention is that a simple pair of iterators may qualify as a valid range. The section [iterator.requirements.general]/5 explains what can be done with singular iterators, and comparing them for equality is not listed as one of the valid operations. Therefore, singular ranges are not "empty". In terms of EoP, they are partially formed objects that must be assigned to before they can be used [8]. - -A particular model of the Range concept may choose to provide this guarantee, but it is not required by the Range concept itself and, as such, should not be relied upon. - -Concept Definitions -===== - -The following concepts are proposed to constrain the standard library. The iterator concepts mentioned here are identical to those specified in [N3351][8][@n3351] except where specified. - -## Iterator Concepts - -The range concepts presented below build on the following iterator concepts. These are largely as found in [N3351][8][@n3351], with the addition of WeakIterator, Iterator, WeakOutputIterator and OutputIterator. The entire hierarchy is presented here for completeness. An important, new concept is: - - concept WeakIterator = - WeaklyIncrementable && Copyable && - requires(I i) { - { *i } -> auto&&; - }; - -A WeakIterator is a WeaklyIncrementable type that is both copyable and dereferencable, in addition to being pre-incrementable. The only requirement on the result of dereferencing the iterator is that its type can be deduced from `auto&&`. Effectively, the result of dereferencing shall not be `void`. The WeakIterator concept allows iterator adaptors to provide a dereferencing operator for both InputIterators and OutputIterators. - -This concept is refined by the addition of either Readable or Writable, which allow reading from and writing to a dereferenced iterator, respectively. The Iterator and WeakIterator also obviate the need for separate InputRange and OutputRange concepts (as we see later). - - concept Iterator = - WeakIterator && EqualityComparable; - - concept WeakOutputIterator = - WeakIterator && Writable; - - concept OutputIterator = - WeakOutputIterator && EqualityComparable; - -N3351 does not define WeakOutputIterator or OutputIterator, preferring instead to define all the algorithms in terms of WeaklyIncrementable and (separately) Writable. We see no disadvantage to rolling these two into the familiar (Weak)OutputIterator concept as a convenience; it saves typing. - -The WeakInputIterator concept defines requirements for a type whose referred to values can be read (from the requirement for Readable) and which be both pre- and post-incremented. However, WeakInputIterators are not required to be compared for equality. There are a number of algorithms whose input ranges are defined by the equality of comparison of another input range (e.g., `std::mismatch` and `std::equal`). - - concept WeakInputIterator = - Readable && - requires(I i) { - typename IteratorCategory; - { i++ } -> Readable; - requires Derived, weak_input_iterator_tag>; - }; - -An InputItreator is a WeakInputIterator that can be compared for equality comparison. This concept defines the basic requirements for a very large number of the algorithm in the standard library. - - concept InputIterator = - WeakInputIterator && - EqualityComparable && - Derived, input_iterator_tag>; - -The ForwardIterator concept refines the InputIterator concept, but the refinement is *semantic*. It guarantees the "multipass" property: it allows multiple iterations of a range of elements. This is something that cannot be done with, say, an `istream_iterator`. The current element is consumed when the iterator is incremented. - - concept ForwardIterator = - InputIterator && - Incrementable && - Derived, forward_iterator_tag>; - -The BidirectionalIterator concept refines the ForwardIterator concept, and allows both incrementing and decrementing. - - concept BidirectionalIterator = - ForwardIterator && - Derived, bidirectional_iterator_tag> && - requires decrement (I i, I j) { - { ––i } -> I&; - { i–– } -> I; - }; - -The RandomAccess concept refines the BidirectionalIterator concept and provides support for constant-time advancement using `+=`, `+`, and `-=`, and the computation of distance in constant time using `-`. Random access iterators also support array notation via subscripting. - - concept RandomAccessIterator = - BidirectionalIterator && - TotallyOrdered && - Derived, random_access_iterator_tag> && - SignedIntegral> && - SizedIteratorRange && // see below - requires advance (I i, I j, DifferenceType n) { - { i += n } -> I&; - { i + n } -> I; - { n + i } -> I; - { i –= n } -> I&; - { i – n } -> I; - { i[n] } -> ValueType; - }; - -## Iterator Range Concepts - -The IteratorRange concept defines a pair of types (an Iterator and a Sentinel), that can be compared for equality. This concept is the key that allows iterator ranges to be defined by pairs of types that are not the same. - - concept IteratorRange = - Iterator && - Regular && - EqualityComparable; - -The SizedIteratorRange allows the use of the `-` operator to compute the distance between to an Iterator and a Sentinel. - - concept SizedIteratorRange = - IteratorRange && - requires difference (I i, S j) { - typename Distance_type; - { i - i } -> Distance_type; - { j - j } -> Distance_type; - { i - j } -> Distance_type; - { j - i } -> Distance_type; - requires SignedIntegral; - }; - -## Iterable Concepts - -This proposal introduces a new concept for describing the requirements on algorithms that require access to the `begin` and `end` of a Range or Container. - - concept Iterable = - requires(T t) { - typename IteratorType; - typename SentinelType; - { begin(t) } -> IteratorType; - { end(t) } -> SentinelType; - requires IteratorRange; - } - -The Iterable concept requires two associated operations: `begin`, and `end`. Note that the return types of these operations are not required to be the same. The names of those return types are `IteratorType` and `SentinelType`, respectively. Furthermore, that pair of types must satisfy the requirements of the IteratorRange concept defined in the previous section. - -Iterable types have no other requirements. Most algorithms requiring this concept simply forward to an Iterator-based algorithm by calling `begin` and `end`. - -The Range concept refines the Iterable concept and defines a view on a range of elements that it does not own. - - concept Range = - Iterable && Semiregular && is_range::value; - -A possible implementation of the `is_range` predicate is shown below. - - // For exposition only: - struct range_base - {}; - - concept ContainerLike = - !Same())), - decltype(*begin(declval()))>; - - // For exposition only: - template - struct is_range_impl_ - : std::integral_constant< - bool, - Iterable && (!ContainerLike || Derived) - > - {}; - - // Specialize this if the default is wrong. - template - struct is_range - : conditional< - is_same>>::value, - is_range_impl_, - is_range>> - >::type - {}; - - -Note that the "multipass" property--the property that guarantees that you can iterate the same range twice, accessing the same objects in each pass--is guaranteed by the `forward_iterator_tag` class. That is, any iterator having whose iterator category is derived from that tag class is required to be a multipass iterator. - - -## Sized Iterable Concepts - -The `SizedIterable` concept exists for the same reasons as `SizedIteratorRange`. There are some iterables that, though they are not random-access, know their size in O(1). A prime example is `std::list`. Another example is a counted view (i.e., a range specified by an iterator and a count). Some algorithms can select a better implementation when the size of the range is known, even if the iterators don't allow random access. (One example is a `search` algorithm that knows to stop searching when there is no longer room in the input sequence for the pattern to match.) - - // For exposition only: - concept SizedIterableLike = - requires(R r) { - typename SizeType; - { size(r) } -> SizeType; // ADL customization point - requires Integral; - } - - concept SizedIterable = - is_sized_iterable::value; - -Like [N4017][20][@n4017], we propose `size(r)` as a customization point for accessing the size of an Iterable. A SizedIterable requires that `size(r)` returns the same value as `distance(begin(r), end(r))` (module a possible change in the signed-ness of the Integral type). As in N4017, we suggest `size` overloads in `std::` for arrays and for class types with a `size()` member. - -In recognition of the fact that nominal conformance to a concept is insufficiently fine-grained, the SizedIterable concept is defined in terms of a `is_sized_iterable` trait. This is to give users explicit control in the case of accidental conformance with the syntactic requirements of the concept. A possible default implementation of the `is_sized_iterable` trait is shown below. - - // For exposition only: - template - struct is_sized_iterable_impl_ - : std::integral_constant< - bool, - SizedIterableLike - > - {}; - - // Specialize this if the default is wrong. - template - struct is_sized_iterable - : conditional< - is_same>>::value, - is_sized_iterable_impl_, - is_sized_iterable>> - >::type - {}; - -Technical Specifications -===== - -This section is intentionally left blank. - -Future Directions -===== - -More work is necessary to get ranges into the standard. Subsequent proposals will recommend specific components for standardization as described in [Impact on the Standard](#impact-on-the-standard). In addition, below are some additional areas that are currently open for research. - -- Range extensions to things like regex. Make it work with null-terminated strings, e.g.. -- Discuss the pros and cons of tying this work with work on a concept-ified standard library (aka Concepts Lite TS2). -- Discuss the pros and cons of making this (and the concept-ified standard library) *strictly* backwards compatible, versus delivering the new, conceptified and range-ified standard library in a separate, versioned namespace (and what such a solution should look like). -- Discuss how an extension to the Concepts Lite proposal, implicit conversion to concept, bears on the Iterable/Range concepts and the way the algorithms are constrained. Also discuss how it can give a syntax to express "this function does not mutate the range elements." -- Assess the impact of allowing ForwardIterators to return proxies. -- Consider integrating ContiguousIterator from [N4132][18][@n4132] - -Acknowledgements -===== - -I would like to give special thanks to Sean Parent for his advice and feedback on early designs of the range library on which this proposal is based, in addition to his work on the [Adobe Source Libraries][3][@asl] from which this proposal has borrowed liberally. - -Also deserving of special thanks is Andrew Sutton. His work on Concepts Lite and on the formulations of the algorithms as specified in [N3351][8][@n3351] has proven invaluable, and he has generously donated his time and expertise to expound on the ideas there and improve the quality of this proposal. - -Chandler Carruth has also helped more than he probably knows. I am indebted to him for his support and perspective. - -I would be remiss if I didn't acknowledge the foundational work of all the people whose ideas and sweat have gone into various range libraries and proposals in the past. They are too many to list, but I certainly benefited from the work of Dave Abrahams, Dietmar Kühl, Neil Groves, Thorsten Ottosen, Arno Schoedl, Daniel Walker, and Jeremy Seik. Neil Groves also submitted a particularly extensive review of this document. - -Of course none of this work would be possible without Alex Stepanov's giant leap forward with the STL, or without Bjarne Stroustrup who gave Alex the instrument he needed to most clearly realize his vision. - -References -===== - ---- -references: -- id: boostconceptcheck - title: Boost Concept Check Library - URL: 'http://boost.org/libs/concept_check' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: boostrange - title: Boost.Range Library - URL: 'http://boost.org/libs/range' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: asl - title: Adobe Source Libraries - URL: 'http://stlab.adobe.com' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: drange - title: D Phobos std.range - URL: 'http://dlang.org/phobos/std_range.html' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: bekennrange - title: Position-Based Ranges - URL: 'https://github.com/Bekenn/range' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: stepanov09 - title: Elements of Programming - type: book - author: - - family: Stepanov - given: Alexander - - family: McJones - given: Paul - edition: 1 - isbn: 032163537X, 9780321635372 - issued: - year: 2009 - publisher: Addison-Wesley Professional -- id: n3351 - title: 'N3351: A Concept Design for the STL' - type: article - author: - - family: Stroustrup - given: Bjarne - - family: Sutton - given: Andrew - issued: - year: 2012 - month: 1 - URL: 'http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3351.pdf' -- id: n1873 - title: 'N1873: The Cursor/Property Map Abstraction' - type: article - author: - - family: Dietmar - given: Kühl - - family: Abrahams - given: David - issued: - year: 2005 - month: 8 - day: 26 - URL: 'http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1873.html' -- id: n3782 - title: 'N3782: Index-Based Ranges' - type: article - author: - - family: Schödl - given: Arno - - family: Fracassi - given: Fabio - issued: - year: 2013 - month: 9 - day: 24 - URL: 'http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3782.pdf' -- id: clangmodernize - title: Clang Modernize - URL: 'http://clang.llvm.org/extra/clang-modernize.html' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: new-iter-concepts - title: New Iterator Concepts - URL: 'http://www.boost.org/libs/iterator/doc/new-iter-concepts.html' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: universal-references - title: Universal References in C++11 - URL: 'http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: range-comprehensions - title: Range Comprehensions - URL: 'http://ericniebler.com/2014/04/27/range-comprehensions/' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: n4132 - title: 'N4132: Contiguous Iterators' - type: article - author: - - family: Maurer - given: Jens - issued: - year: 2014 - month: 9 - day: 10 - accessed: - year: 2014 - month: 10 - day: 8 - URL: 'https://isocpp.org/files/papers/n4132.html' -- id: ntcts-iterator - title: NTCTS Iterator - URL: 'https://github.com/Beman/ntcts_iterator' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: range-v3 - title: Range v3 - URL: 'http://www.github.com/ericniebler/range-v3' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: llvm-sroa - title: 'Debug info: Support fragmented variables' - URL: 'http://reviews.llvm.org/D2680' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: libcxx - title: 'libc++ C++ Standard Library' - URL: 'http://libcxx.llvm.org/' - type: webpage - accessed: - year: 2014 - month: 10 - day: 8 -- id: austern98 - title: 'Segmented Iterators and Hierarchical Algorithms' - URL: 'http://dl.acm.org/citation.cfm?id=647373.724070' - author: - - family: Austern - given: Matthew - type: paper-conference - container-title: Selected Papers from the International Seminar on Generic Programming - page: 80-90 - issued: - year: 2000 -- id: cpp-seasoning - title: 'C++ Seasoning' - author: - - family: Parent - given: Sean - type: speech - URL: 'https://github.com/sean-parent/sean-parent.github.com/wiki/presentations/2013-09-11-cpp-seasoning/cpp-seasoning.pdf' - container-title: 'GoingNative 2013' - issued: - year: 2013 - month: 9 - day: 11 -- id: muchnick97 - title: 'Advanced Compiler Design Implementation' - author: - - family: Muchnick - given: Steven - publisher: 'Morgan Kaufmann' - issued: - year: 1997 - isbn: '1558603204, 9781558603202' -- id: n4017 - title: 'N4017: Non-member size() and more' - type: article - author: - - family: Marcangelo - given: Riccardo - issued: - year: 2014 - month: 5 - day: 22 - accessed: - year: 2014 - month: 10 - day: 10 - URL: 'http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4017.htm' -- id: n3350 - title: 'N3350: A minimal std::range' - type: article - author: - - family: Yasskin - given: Jeffrey - issued: - year: 2012 - month: 1 - day: 16 - accessed: - year: 2014 - month: 10 - day: 10 - URL: 'http://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2012/n3350.html' -... - -[1]: http://boost.org/libs/concept_check "Boost Concept Check Library" -[2]: http://www.boost.org/libs/range "Boost.Range" -[3]: http://stlab.adobe.com/ "Adobe Source Libraries" -[4]: http://dlang.org/phobos/std_range.html "D Phobos std.range" -[5]: https://github.com/Bekenn/range "Position-Based Ranges" -[6]: https://github.com/sean-parent/sean-parent.github.com/wiki/presentations/2013-09-11-cpp-seasoning/cpp-seasoning.pdf "C++ Seasoning, Sean Parent" -[7]: http://www.github.com/ericniebler/range-v3 "Range v3" -[8]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3351.pdf "A Concept Design for the STL" -[9]: http://www.boost.org/libs/iterator/doc/new-iter-concepts.html "New Iterator Concepts" -[10]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3782.pdf "Indexed-Based Ranges" -[11]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1873.html "The Cursor/Property Map Abstraction" -[12]: http://ericniebler.com/2014/04/27/range-comprehensions/ "Range Comprehensions" -[13]: http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers "Universal References in C++11" -[14]: http://lafstern.org/matt/segmented.pdf "Segmented Iterators and Hierarchical Algorithms" -[15]: http://reviews.llvm.org/D2680 "Debug info: Support fragmented variables." -[16]: http://clang.llvm.org/extra/clang-modernize.html "Clang Modernize" -[17]: http://libcxx.llvm.org/ "libc++ C++ Standard Library" -[18]: https://isocpp.org/files/papers/n4132.html "Contiguous Iterators" -[19]: https://github.com/Beman/ntcts_iterator "ntcts_iterator" -[20]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4017.htm "Non-member size() and more" -[21]: http://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2012/n3350.html "A minimal std::range" - -Appendix 1: Sentinels and Code Generation -===== - -In this appendix we explore the effect of sentinels on code generation. I'll show that allowing the type of the end iterator to differ from the begin can have a positive effect on the performance of algorithms. First, I'll note that nothing that can be done with sentinels cannot also be done with appropriately designed end iterators. Here, for instance, is the code for an iterator that can be used to adapt a null-terminated string to the STL. It is implemented with the help of the Boost.Iterators library: - - #include - #include - #include - - struct c_string_range - { - private: - char const *str_; - public: - using const_iterator = struct iterator - : boost::iterator_facade< - iterator - , char const - , std::forward_iterator_tag - > - { - private: - friend class boost::iterator_core_access; - friend struct c_string_range; - char const * str_; - iterator(char const * str) - : str_(str) - {} - bool equal(iterator that) const - { - return str_ - ? (that.str_ == str_ || - (!that.str_ && !*str_)) - : (!that.str_ || !*that.str_); - } - void increment() - { - assert(str_ && *str_); - ++str_; - } - char const& dereference() const - { - assert(str_ && *str_); - return *str_; - } - public: - iterator() - : str_(nullptr) - {} - }; - c_string_range(char const * str) - : str_(str) - { - assert(str_); - } - iterator begin() const - { - return iterator{str_}; - } - iterator end() const - { - return iterator{}; - } - explicit operator bool() const - { - return !!*str_; - } - }; - - int c_strlen(char const *sz) - { - int i = 0; - for(; *sz; ++sz) - ++i; - return i; - } - - int range_strlen( - c_string_range::iterator begin, - c_string_range::iterator end) - { - int i = 0; - for(; begin != end; ++begin) - ++i; - return i; - } - -The code traverses the sequence of characters without first computing its end. It does it by creating a dummy end iterator such that any time a real iterator is compared to it, it checks to see if the real iterator points to the null terminator. All the comparison logic is in the `c_string_range::iterator::equal` member function. - -The functions `c_strlen` and `range_strlen` implement equivalent procedures for computing the length of a string, the first using raw pointers and a check for the null terminator, the second using `c_string_range`'s STL iterators. The resulting optimized assembly code (clang 3.4 -O3 -DNDEBUG) generated for the two functions highlights the lost optimization opportunities. - - - - -
c_strlen
range_strlen
    pushl   %ebp
-    movl    %esp, %ebp
-    movl    8(%ebp), %ecx
-    xorl    %eax, %eax
-    cmpb    $0, (%ecx)
-    je      LBB1_3
-    xorl    %eax, %eax
-    .align  16, 0x90
-LBB1_2:
-    cmpb    $0, 1(%ecx,%eax)
-    leal    1(%eax), %eax
-    jne     LBB1_2
-LBB1_3:
-    popl    %ebp
-    ret
    pushl   %ebp
-    movl    %esp, %ebp
-    pushl   %esi
-    leal    8(%ebp), %ecx
-    movl    12(%ebp), %esi
-    xorl    %eax, %eax
-    testl   %esi, %esi
-    movl    8(%ebp), %edx
-    jne     LBB2_4
-    jmp     LBB2_1
-    .align  16, 0x90
-LBB2_8:
-    incl    %eax
-    incl    %edx
-    movl    %edx, (%ecx)
-LBB2_4:
-    testl   %edx, %edx
-    jne     LBB2_5
-    cmpb    $0, (%esi)
-    jne     LBB2_8
-    jmp     LBB2_6
-    .align  16, 0x90
-LBB2_5:
-    cmpl    %edx, %esi
-    jne     LBB2_8
-    jmp     LBB2_6
-    .align  16, 0x90
-LBB2_3:
-    leal    1(%edx,%eax), %esi
-    incl    %eax
-    movl    %esi, (%ecx)
-LBB2_1:
-    movl    %edx, %esi
-    addl    %eax, %esi
-    je      LBB2_6
-    cmpb    $0, (%esi)
-    jne     LBB2_3
-LBB2_6:
-    popl    %esi
-    popl    %ebp
-    ret
- -Code like `c_string_range` exists in the wild; for instance, see [Beman Dawes' `ntcts_iterator`][19][@ntcts-iterator]. But more typically, when users want to use an STL algorithm on a C-style string, they call `strlen` to find the end first (this is what the standard regex algorithms do when passed C-style strings). That traverses the string an extra time needlessly. Also, such a trick is not possible for input sequences like those traversed by `std::istream_iterator` that consume their input. - -Rather than mocking up a dummy end iterator with a computationally expensive equality comparison operation, we can use a sentinel type that encodes end-ness in its type. Below is an example from the [Range-v3 library][7][@range-v3], which uses a `range_facade` class template to generate iterators and sentinels from a simple range-like interface: - - using namespace ranges; - struct c_string_iterable - : range_facade - { - private: - friend range_access; - char const *sz_; - char const & current() const { return *sz_; } - void next() { ++sz_; } - bool done() const { return *sz_ == 0; } - bool equal(c_string_iterable const &that) const - { return sz_ == that.sz_; } - public: - c_string_iterable() = default; - c_string_iterable(char const *sz) - : sz_(sz) {} - }; - - // Iterable-based - int iterable_strlen( - range_iterator_t begin, - range_sentinel_t end) - { - int i = 0; - for(; begin != end; ++begin) - ++i; - return i; - } - -The assembly generated for `iterable_strlen` is nearly identical to that for the hand-coded `c_strlen`: - - - - -
c_strlen
iterable_strlen
    pushl   %ebp
-    movl    %esp, %ebp
-    movl    8(%ebp), %ecx
-    xorl    %eax, %eax
-    cmpb    $0, (%ecx)
-    je      LBB1_3
-    xorl    %eax, %eax
-    .align  16, 0x90
-LBB1_2:
-    cmpb    $0, 1(%ecx,%eax)
-    leal    1(%eax), %eax
-    jne     LBB1_2
-
-
-LBB1_3:
-    popl    %ebp
-    ret
    pushl   %ebp
-    movl    %esp, %ebp
-    movl    8(%ebp), %ecx
-    xorl    %eax, %eax
-    cmpb    $0, (%ecx)
-    je      LBB1_4
-    leal    8(%ebp), %edx
-    .align  16, 0x90
-LBB1_2:
-    cmpb    $0, 1(%ecx,%eax)
-    leal    1(%eax), %eax
-    jne     LBB1_2
-    addl    %eax, %ecx
-    movl    %ecx, (%edx)
-LBB1_4:
-    popl    %ebp
-    ret
- -The generated code for `iterable_strlen` is better than for `range_strlen` because the sentinel has a different type than the iterator, so that the expression `begin != end` can be optimized into `*begin == 0`. In `range_strlen`, `begin == end` is comparing two objects of the same type, and since either `begin` or `end` could be a sentinel -- or both, or neither -- the compiler can't elide the extra checks without extra information from the surrounding calling context, which isn't always available; hence, the worse code gen. - -In addition to the performance impact, the complexity of implementing a correct `operator==` for an iterator with a dummy sentinel can present problems. Chandler Carruth reports that such comparison operators have been a rich source of bugs for Google. - -Appendix 2: Sentinels, Iterators, and the Cross-Type EqualityComparable Concept -===== - -This appendix describes the theoretical justification for sentinels from the perspective of the STL concepts as set out in [N3351][8][@n3351]. In that paper, the foundational concept EqualityComparable is described in depth, not only its syntactic constraints but also its semantic axioms. It is not enough that the syntax `a == b` compiles. It has to be a meaningful comparison. Here I explain why I believe it is meaningful to compare an iterator with a sentinel of a different type for equality. - -In the expression `x == y`, where `x` and `y` have different types, the EqualityComparable concept requires that the types of both `x` and `y` must themselves be EqualityComparable, and there must be a common type to which they can both be converted, and that type must also be EqualityComparable. Think of comparing a `char` with a `short`. It works because both `char` and `short` are EqualityComparable, and because they can both be converted to an `int` which is also EqualityComparable. - -Iterators are comparable, and sentinels are trivially comparable (they always compare equal). The tricky part is the common type requirement. Logically, every iterator/sentinel pair has a common type that can be constructed as follows: assume the existence of a new iterator type `I` that is a tagged union that contains *either* an iterator *or* a sentinel. When an iterator is compared to a sentinel, it behaves semantically as if both the iterator and the sentinel were first converted to two objects of type `I` — call them `lhs` and `rhs` — and then compared according to the following truth table: - -LHS IS SENTINEL ? | RHS IS SENTINEL ? | LHS == RHS ? ------------------ | ----------------- | ------------ -`true` | `true` | `true`[\[*\]](#sentinel-equality "Sentinel Equality") -`true` | `false` | `done(rhs.iter)` -`false` | `true` | `done(lhs.iter)` -`false` | `false` | `lhs.iter == rhs.iter` - -In [Appendix 1](#appendix-1-sentinels-and-code-generation), there is an implementation of `c_string_range` whose iterator's `operator==` is a procedure for evaluating this truth table. That’s no coincidence; that was a special case of this more general construction. - -In summary, for every iterator/sentinel pair, we can construct a common iterator type that implements an equivalent procedure for computing equality. The existence of this common type is what allows iterator/sentinel pairs to satisfy the EqualityComparable requirement. - -As a final note, the Range v3 library has a general implementation of this common iterator type as a parametrized type, and appropriate specializations of `std::common_type` that allow the constrained algorithms to type-check correctly. It works well in practice, both for the purpose of type-checking the algorithms and for adapting ranges with iterator/sentinel pairs to old code that expects the begin and end of a range to have the same type. - -## Sentinel Equality - -The first line of table above shows that sentinels always compare equal -- regardless of their state. This can seem unintuitive in some situations. For instance: - - auto s = "Hello World!?"; - // Hypothetical range-like facilities for exposition only: - auto r1 = make_range( &s[0], equal_to('!') ); - auto r2 = make_range( &s[0], equal_to('?') ); - // 'end()' returns each 'equal_to' sentinel object: - assert(r1.end() == r2.end() && "really?"); - -In the above example, it's clear that `r1.end()` and `r2.end()` are referring to different elements, and that's reflected in their having different state. So shouldn't sentinel equality be a stateful comparison? - -It's incorrect to think of sentinels as objects whose position is determined by their value. Thinking that way leads to logical inconsistencies. It's not hard to define sentinels that -- if you consider their state during comparison -- compare equal when they represent a different position, and not equal when they represent the same position. Consider: - - auto s = "hello! world!"; - auto r1 = make_range( &s[0], equal_to('!') ); - auto r2 = make_range( &s[7], equal_to('!') ); - - assert(r1.end() == r2.end() && "really?"); - -In the above code, although the sentinels have the same type and the same state, they refer to different elements. - -Also imagine a counted iterator that stores an underlying iterator and a count that starts at zero. It's paired with a sentinel that contains an ending count: - - auto s = "hello"; - // Hypothetical range-like facilities for exposition only: - auto r1 = make_range( counting(&s[0]), end_count(5) ); - auto r2 = make_range( counting(&s[1]), end_count(4) ); - - assert(r1.end() != r2.end() && "really?"); - -The end sentinels have the same type and different state, but they refer to the same element. - -The question then is: What does it *mean* to compare sentinels for equality? The meaning has to be taken from the context in which the operation appears. In that context -- the algorithms -- the sentinel is one of two positions denoting a range. - -Iterator comparison is a position comparison. A sentinel, when it's paired with an iterator as in the algorithms, denotes a unique position. Since iterators must be valid sentinels, comparing sentinels should also be a position comparison. In this context, sentinels always represent the same position -- the end -- even though that position is not yet know. Hence, sentinels compare equal always, without any need to consider state. Anything else yields results that are inconsistent with the use of iterators to denote the end of a range. - -What about the examples above where sentinel comparison seems to give nonsensical results? In those examples, we are expecting sentinels to have meaning outside of their valid domain -- when they are considered in isolation of their paired iterator. It is the paired iterator that give the sentinel its "unique-position-ness", so we shouldn't expect an operation that compares position to make sense on an object that is not capable of representing a position by itself. - -Appendix 3: D Ranges and Algorithmic Complexity -===== - -As currently defined, D's ranges make it difficult to implement algorithms over bidirectional sequences that take a position in the middle as an argument. A good example is a hypothetical `is_word_boundary` algorithm. In C++ with iterators, it might look like this: - - template< BidirectionalIterator I > - bool is_word_boundary( I begin, I middle, I end ) - { - bool is_word_prev = middle == begin ? false : isword(*prev(middle)); - bool is_word_this = middle == end ? false : isword(*middle); - return is_word_prev != is_word_this; - } - -Users might call this in a loop to find the first word boundary in a range of characters as follows: - - auto i = myrange.begin(); - for( ; i != myrange.end(); ++i ) - if( is_word_boundary( myrange.begin(), i, myrange.end() ) ) - break; - -The question is how such an API should be designed in D, since D doesn't have iterators. In a private email exchange, Andrei Alexandrescu, the designer of D's range library, described this potential implementation (the detailed implementation is ours): - - bool is_word_boundary(Range1, Range2)( Range1 front, Range2 back ) - if (isBidirectionalRange!Range1 && isInputRange!Range2 ) - { - bool is_word_prev = front.empty ? false : isword(front.back); - bool is_word_this = back.empty ? false : isword(back.front); - return is_word_prev != is_word_this; - } - - range r = myrange; - size_t n = 0; - for(range r = myrange; !r.empty; r.popFront(), ++n) - if( is_word_boundary( takeExactly(myrange, n), r) ) - break; - -This example uses D's `takeExactly` range adaptor. `takeExactly` is like Haskell's `take` function which creates a list from another list by taking the first `n` elements, but `takeExactly` requires that the input range has at least `n` elements begin with. - -The trouble with the above implementation is that `takeExactly` demotes Bidirectional ranges to Forward ranges. For example, taking the first *N* elements of a linked list cannot give access to the final element in O(1). So the `for` loop is trying to pass a Forward range to an algorithm that clearly requires a Bidirectional range. The only fix is to loosen the requirements of `Range1` to be Forward. To do that, the implementation of `is_word_boundary` needs to change so that, when it is passed a Forward range, it walks to the end of the `front` range and tests the last character. Obviously, that's an O(N) operation. - -In other words, by converting the `is_word_boundary` from iterators to D-style ranges, the algorithm goes from O(1) to O(N). From this we draw the following conclusion: D's choice of algorithmic *basis operations* is inherently less efficient than C++'s. - -Appendix 4: On Counted Ranges and Efficiency -===== - -The three types of ranges that we would like the Iterable concept to be able to efficiently model are: - -1. Two iterators -2. An iterator and a predicate -3. An iterator and a count - -The Iterator/Sentinel abstraction is what makes it possible for the algorithms to handle these three cases with uniform syntax. However, the third option presents challenges when trying to make some algorithms optimally efficient. - -Counted ranges, formed by specifying a position and a count of elements, have iterators -- as all Iterables do. The iterators of a counted range must know the range's extent and how close they are to reaching it. Therefore, the counted range's iterators must store both an iterator into the underlying sequence and a count -- either a count to the end or a count from the front. Here is one potential design: - - class counted_sentinel - {}; - - template - class counted_iterator - { - I it_; - DistanceType n_; // distance to end - public: - // ... constructors... - using iterator_category = - typename iterator_traits::iterator_category; - decltype(auto) operator*() const - { - return *it_; - } - counted_iterator & operator++() - { - ++it_; - --n_; - return *this; - } - friend bool operator==(counted_iterator const & it, - counted_sentinel) - { - return it.n_ == 0; - } - // ... other operators... - }; - - template - class counted_range - { - I begin_; - DistanceType count_; - public: - // ... constructors ... - counted_iterator begin() const - { - return {begin_, count_}; - } - counted_sentinel end() const - { - return {}; - } - }; - - template - struct common_type, counted_sentinel> - // ... see Appendix 2 ... - -There are some noteworthy things about the code above. First, `counted_iterator` bundles an iterator and a count. Right off, we see that copying counted iterators is going to be more expensive, and iterators are copied frequently. A mitigating factor is that the sentinel is empty. Passing a `counted_iterator` and a `counted_sentinel` to an algorithm copies as much data as passing an iterator and a count. When passed separately, the compiler probably has an easier time fitting them in registers, but some modern compilers are capable passing the members of a struct in registers. This compiler optimization is sometimes called Scalar Replacement of Aggregates (see Muchnick 1997, ch. 12.2 [@muchnick97]) and is known to be implemented in gcc and LLVM (see [this recent LLVM commit][15][@llvm-sroa] for example). - -Also, incrementing a counted iterator is expensive: it involves incrementing the underlying iterator and decrementing the internal count. To see why this is potentially expensive, consider the trivial case of passing a `counted_iterator::iterator>` to `advance`. That counted iterator type is bidirectional, and `advance` must increment it *n* times: - - template - void advance(I & i, DistanceType n) - { - if(n >= 0) - for(; n != 0; --n) - ++i; - else - for(; n != 0; ++n) - --i; - } - -Notice that for each `++i` or `--i` here, *two* increments or decrements are happening when `I` is a `counted_iterator`. This is sub-optimal. A better implementation for `counted_iterator` is: - - template - void advance(counted_iterator & i, DistanceType n) - { - i.n_ -= n; - advance(i.it_, n); - } - -This has a noticeable effect on the generated code. As it turns out, `advance` is one of the relatively few places in the standard library where special handling of `counted_iterator` is advantageous. Let's examine some algorithms to see why that's the case. - -## Single-Pass Algorithms with Counted Iterators - -First, let's look at a simple algorithm like `for_each` that makes exactly one pass through its input sequence: - - template> F> - requires EqualityComparable - I for_each(I first, S last, F f) - { - for(; first != last; ++first) - f(*first); - return first; - } - -When passed counted iterators, at each iteration of the loop, we do an increment, a decrement (for the underlying iterator and the count), and a comparison. Let's compare this against a hypothetical `for_each_n` algorithm that takes the underlying iterator and the count separately. It might look like this: - - template> F> - I for_each_n(I first, DifferenceType n, F f) - { - for(; n != 0; ++first, --n) - f(*first); - return first; - } - -For the hypothetical `for_each_n`, at each loop iteration, we do an increment, a decrement, and a comparison. That's exactly as many operations as `for_each` does when passed counted iterators. So a separate `for_each_n` algorithm is probably unnecessary if we have sentinels and `counted_iterator`s. This is true for any algorithm that makes only one pass through the input range. That turns out to be a lot of algorithms. - -## Multi-Pass Algorithms with Counted Iterators - -There are other algorithms that make more than one pass over the input sequence. Most of those, however, use `advance` when they need to move iterators by more than one hop. Once we have specialized `advance` for `counted_iterator`, those algorithms that use `advance` get faster without any extra work. - -Consider `partition_point`. Here is one example implementation, taken from [libc++][17][@libcxx] and ported to Concepts Lite and sentinels: - - template> P> - requires EqualityComparable - I partition_point(I first, S last, P pred) - { - DifferenceType len = distance(first, last); - while (len != 0) - { - DifferenceType l2 = len / 2; - I m = first; - advance(m, l2); - if (pred(*m)) - { - first = ++m; - len -= l2 + 1; - } - else - len = l2; - } - return first; - } - -Imagine that `I` is a forward `counted_iterator` and `S` is a `counted_sentinel`. If the library does not specialize `advance`, this is certainly inefficient. Every time `advance` is called, needless work is being done. Compare it to a hypothetical `partition_point_n`: - - template> P> - I partition_point_n(I first, DifferenceType len, P pred) - { - while (len != 0) - { - DifferenceType l2 = len / 2; - I m = first; - advance(m, l2); - if (pred(*m)) - { - first = ++m; - len -= l2 + 1; - } - else - len = l2; - } - return first; - } - -The first thing we notice is that `partition_point_n` doesn't need to call `distance`! The more subtle thing to note is that calling `partition_point_n` with a raw iterator and a count saves about O(N) integer decrements over the equivalent call to `partition_point` with `counted_iterator`s ... unless, of course, we have specialized the `advance` algorithm as shown above. Once we have, we trade the O(N) integer decrements for O(log N) integer subtractions. That's a big improvement. - -But what about the O(N) call to `distance`? Actually, that's easy, and it's the reason why the SizedIteratorRange concept exists. `counted_iterator` stores the distance to the end. So the distance between a `counted_iterator` and a `counted_sentinel` (or between two `counted_iterators`) is known in O(1) *regardless of the iterator's category*. The SizedIteratorRange concept tests whether an iterator `I` and a sentinel `S` can be subtracted to get the distance. This concept is modeled by random-access iterators by their nature, but also by counted iterators and their sentinels. The `distance` algorithm is specialized for SizedIteratorRange, so it is O(1) for counted iterators. - -With these changes, we see that `partition_point` with counted iterators is very nearly as efficient as a hypothetical `partition_point_n` would be, and we had to make no special accommodations. Why can't we make `partition_point` *exactly* as efficient as `partition_point_n`? When `partition_point` is called with a counted iterator, it also *returns* a counted iterator. Counted iterators contain two datums: the position and distance to the end. But when `partition_point_n` returns just the position, it is actually computing and returning less information. Sometimes users don't need the extra information. But sometimes, after calling `partition_point_n`, the user might want to pass the resulting iterator to another algorithm. If *that* algorithm calls `distance` (like `partition_point` and other algorithms do), then it will be O(N). With counted iterators, however, it's O(1). So in the case of `partition_point`, counted iterators cause the algorithm to do O(log N) extra work, but it sometimes saves O(N) work later. - -To see an example, imagine a trivial `insertion_sort` algorithm: - - template - requires EqualityComparable && Sortable // from N3351 - void insertion_sort(I begin, S end) - { - for(auto it = begin; it != end; ++it) - { - auto insertion = upper_bound(begin, it, *it); - rotate(insertion, it, next(it)); - } - } - -Imagine that `I` is a `counted_iterator`. The first thing `upper_bound` does is call `distance`. Making `distance` O(1) for `counted_iterator`s saves N calls of an O(N) algorithm. To get comparable performance for an equivalent procedure in today's STL, users would have to write a separate `insertion_sort_n` algorithm that dispatches to an `upper_bound_n` algorithm -- that they would also need to write themselves. - -## Counted Algorithms with Counted Iterators - -We've seen that regular algorithms with counted iterators can be made nearly as efficient as dedicated counted algorithms, and that sometimes we are more than compensated for the small performance loss. All is not roses, however. There are a number of *counted algorithms* in the standard (the algorithms whose names end with `_n`). Consider `copy_n`: - - template> O> - pair copy_n(I in, DifferenceType n, O out) - { - for(; n != 0; ++in, ++out, --n) - *out = *in; - return {in, out}; - } - -(We've changed the return type of `copy_n` so as not to lose information.) If `I` is a counted iterator, then for every `++in`, an increment and a decrement are happening, and in this case the extra decrement is totally unnecessary. For *any* counted (i.e., `_n`) algorithm, something special needs to be done to keep the performance from degrading when passed counted iterators. - -The algorithm author has two options here, and neither of them is ideal. - -**Option 1: Overload the algorithm** - -The following is an optimized version of `copy_n` for counted iterators: - - template> O> - pair copy_n(counted_iterator in, DifferenceType n, O out) - { - for(auto m = in.n_ - n; in.n_ != m; ++in.i_, --in.n_, ++out) - *out = *in; - return {in, out}; - } - -Obviously, creating an overload for counted iterators is unsatisfying. - -**Option 2: Separate the iterator from the count** - -This option shows how a library implementer can write just one version of `copy_n` that is automatically optimized for counted iterators. First, we need to provide two utility functions for unpacking and repacking counted iterators: - - template - I uncounted(I i) - { - return i; - } - - template - I uncounted(counted_iterator i) - { - return i.it_; - } - - template - I recounted(I const &, I i, DifferenceType) - { - return i; - } - - template - counted_iterator recounted(counted_iterator const &j, I i, DifferenceType n) - { - return {i, j.n_ - n}; - } - -With the help of `uncounted` and `recounted`, we can write an optimized `copy_n` just once: - - template> O> - pair copy_n(I in_, DifferenceType n_, O out) - { - auto in = uncounted(in_); - for(auto n = n_; n != 0; ++in, --n, ++out) - *out = *in; - return {recounted(in_, in, n_), out}; - } - -This version works optimally for both counted and non-counted iterators. It is not a thing of beauty, however. It's slightly annoying to have to do the `uncounted`/`recounted` dance, but it's mostly needed only in the counted algorithms. - -As a final note, the overload of `advance` for counted iterators can be eliminated with the help of `uncounted` and `recounted`. After all, `advance` is a counted algorithm. - -## Benchmark: Insertion Sort - -To test how expensive counted ranges and counted iterators are, we wrote a benchmark. The benchmark program pits counted ranges against a dedicated `_n` algorithm for [Insertion Sort](http://en.wikipedia.org/wiki/Insertion_sort). - -The program is listed below: - - #include - #include - #include - - class timer - { - private: - std::chrono::high_resolution_clock::time_point start_; - public: - timer() - { - reset(); - } - void reset() - { - start_ = std::chrono::high_resolution_clock::now(); - } - std::chrono::milliseconds elapsed() const - { - return std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - start_); - } - friend std::ostream &operator<<(std::ostream &sout, timer const &t) - { - return sout << t.elapsed().count() << "ms"; - } - }; - - template - struct forward_iterator - { - It it_; - - template - friend struct forward_iterator; - public: - typedef std::forward_iterator_tag iterator_category; - typedef typename std::iterator_traits::value_type value_type; - typedef typename std::iterator_traits::difference_type difference_type; - typedef It pointer; - typedef typename std::iterator_traits::reference reference; - - forward_iterator() : it_() {} - explicit forward_iterator(It it) : it_(it) {} - - reference operator*() const {return *it_;} - pointer operator->() const {return it_;} - - forward_iterator& operator++() {++it_; return *this;} - forward_iterator operator++(int) - {forward_iterator tmp(*this); ++(*this); return tmp;} - - friend bool operator==(const forward_iterator& x, const forward_iterator& y) - {return x.it_ == y.it_;} - friend bool operator!=(const forward_iterator& x, const forward_iterator& y) - {return !(x == y);} - }; - - template - I upper_bound_n(I begin, typename std::iterator_traits::difference_type d, V2 const &val) - { - while(0 != d) - { - auto half = d / 2; - auto middle = std::next(begin, half); - if(val < *middle) - d = half; - else - { - begin = ++middle; - d -= half + 1; - } - } - return begin; - } - - template - void insertion_sort_n(I begin, typename std::iterator_traits::difference_type n) - { - auto m = 0; - for(auto it = begin; m != n; ++it, ++m) - { - auto insertion = upper_bound_n(begin, m, *it); - ranges::rotate(insertion, it, std::next(it)); - } - } - - template - void insertion_sort(I begin, S end) - { - for(auto it = begin; it != end; ++it) - { - auto insertion = ranges::upper_bound(begin, it, *it); - ranges::rotate(insertion, it, std::next(it)); - } - } - - template - void insertion_sort(Rng && rng) - { - ::insertion_sort(std::begin(rng), std::end(rng)); - } - - std::unique_ptr data(int i) - { - std::unique_ptr a(new int[i]); - auto rng = ranges::view::counted(a.get(), i); - ranges::iota(rng, 0); - return a; - } - - void shuffle(int *a, int i) - { - auto rng = ranges::view::counted(a, i); - ranges::random_shuffle(rng); - } - - constexpr int cloops = 3; - - template - void benchmark_n(int i) - { - auto a = data(i); - long ms = 0; - for(int j = 0; j < cloops; ++j) - { - ::shuffle(a.get(), i); - timer t; - insertion_sort_n(I{a.get()}, i); - ms += t.elapsed().count(); - } - std::cout << (int)((double)ms/cloops) << std::endl; - } - - template - void benchmark_counted(int i) - { - auto a = data(i); - long ms = 0; - for(int j = 0; j < cloops; ++j) - { - ::shuffle(a.get(), i); - timer t; - insertion_sort(ranges::view::counted(I{a.get()}, i)); - ms += t.elapsed().count(); - } - std::cout << (int)((double)ms/cloops) << std::endl; - } - - int main(int argc, char *argv[]) - { - if(argc < 2) - return -1; - - int i = std::atoi(argv[1]); - std::cout << "insertion_sort_n (random-access) : "; - benchmark_n(i); - std::cout << "insertion_sort (random-access) : "; - benchmark_counted(i); - std::cout << "\n"; - std::cout << "insertion_sort_n (forward) : "; - benchmark_n>(i); - std::cout << "insertion_sort (forward) : "; - benchmark_counted>(i); - } - -The program implements both `insertion_sort_n`, a dedicated counted algorithm, and `insertion_sort`, a general algorithm that accepts any Iterable, to which we pass a counted range. The latter is implemented in terms of the general-purpose `upper_bound` as provided by the Range v3 library, whereas the former requires a dedicated `upper_bound_n` algorithm, which is also provided. - -The test is run both with raw pointers (hence, random-access) and with an iterator wrapper that only models ForwardIterator. Each test is run three times, and the resulting times are averaged. The test was compiled with `g++` version 4.9.0 with `-O3 -std=gnu++11 -DNDEBUG` and run on a Linux machine. The results are reported below, for N == 30,000: - -  | `insertion_sort_n` | `insertion_sort` ---------------|--------------------|----------------- -random-access | 2.692 s | 2.703 s -forward | 23.853 s | 23.817 s - -The performance difference, if there is any, is lost in the noise. At least in this case, with this compiler, on this hardware, there is no performance justification for a dedicated `_n` algorithm. - -**Summary** - -In short, counted iterators are not a *perfect* abstraction. There is some precedent here. The iterators for `deque`, and for any segmented data structure, are known to be inefficient (see [Segmented Iterators and Hierarchical Algorithms][14], Austern 1998[@austern98]). The fix for that problem, new iterator abstractions and separate hierarchical algorithm implementations, is invasive and is not attempted in any STL implementation we are aware of. In comparison, the extra complications that come with counted iterators seem quite small. For segmented iterators, the upside was the simplicity and uniformity of the Iterator abstraction. In the case of counted ranges and iterators, the upside is the simplicity and uniformity of the Iterable concept. Algorithms need only one form, not separate bounded, counted, and sentinel forms. Our benchmark gives us reasonable assurance that we aren't sacrificing performance for the sake of a unifying abstraction. - -Appendix 5: Drive-By Improvements to the Standard Algorithms -===== - -As we are making changes to the standard algorithms, not all of which are strictly source compatible, here are some other drive-by changes that we might consider making. The changes suggested below have nothing to do with ranges *per se*, but they increase the power and uniformity of the STL and they have proven useful in the Adobe Source Library, so we might consider taking all these changes in one go. - -## Higher-Order Algorithms Should Take Invokables Instead of Functions - -Some algorithms like `for_each` are higher-order; they take functions as parameters. In [N3351][8][@n3351], they are constrained with the `Function` concept which, among other things, requires that its parameters can be used to form a valid callable expression `f(a)`. - -However, consider a class `S` with a member function `Do`, like: - - class S { - public: - void Do() const; - }; - -If we have a `vector` of `S` objects and we want to `Do` all of them, this is what we need to do: - - for_each( v, [](auto & s) { s.Do(); }); - -or, more concisely with a `bind` expression: - - for_each( v, bind(&S::Do, _1) ); - -Note that `bind` is specified in terms of a hypothetical INVOKE utility in [func.require]. Wouldn't it be more convenient if all the algorithms were required to merely take INVOKE-able things -- that is, things that can be passed to `bind` -- as arguments, instead of Functions? Then we can express the above call to `for_each` more concisely as: - - for_each( v, &S::Do ); - -We can define an `invokable` utility function as: - - template - auto invokable(R T::* p) const -> decltype(std::mem_fn(p)) - { - return std::mem_fn(p); - } - - template> - auto invokable(T && t) const -> enable_if_t::value, T> - { - return std::forward(t); - } - - template - using invokable_t = decltype(invokable(std::declval())); - -We can define an Invokable concept as: - - concept Invokable = - Function, As...> && - requires (F f, As... as) { - InvokableResultOf; - InvokableResultOf == invokable(f)(as...); - }; - -The Invokable concept can be used to constrain algorithms instead of the Function concept. The algorithms would need to apply `invokable` to each Invokable argument before invoking it. - -This is pure extension and would break no code. - -## Algorithms Should Take Invokable Projections - -The [Adobe Source Libraries (ASL)][3][@asl] pioneered the use of "projections" to make the algorithms more powerful and expressive by increasing interface symmetry. Sean Parent gives a motivating example in his ["C++ Seasoning" talk][6][@cpp-seasoning], on slide 38. With today's STL, when using `sort` and `lower_bound` together with user-defined predicates, the predicate must sometimes differ. Consider: - - std::sort(a, [](const employee& x, const employee& y) - { return x.last < y.last; }); - auto p = std::lower_bound(a, "Parent", [](const employee& x, const string& y) - { return x.last < y; }); - -Notice the different predicates used in the invocations of `sort` and `lower_bound`. Since the predicates are different, there is a chance they might get out of sync leading to subtle bugs. - -By introducing the use of projections, this code is simplified to: - - std::sort(a, std::less<>(), &employee::last); - auto p = std::lower_bound(a, "Parent", std::less<>(), &employee::last); - -Every element in the input sequence is first passed through the projection `&employee::last`. As a result, the simple comparison predicate `std::less<>` can be used in both places. - -No effort was made in ASL to use projections consistently. This proposal bakes them in everywhere it makes sense. Here are the guidelines to be applied to the standard algorithms: - -- Wherever appropriate, algorithms should optionally take INVOKE-able *projections* that are applied to each element in the input sequence(s). This, in effect, allows users to trivially transform each input sequence for the sake of that single algorithm invocation. -- Algorithms that take two input sequences should (optionally) take two projections. -- For algorithms that optionally accept functions/predicates (e.g. `transform`, `sort`), projection arguments follow functions/predicates. There are no algorithm overloads that allow the user to specify the projection without also specifying a predicate, even if the default would suffice. This is to reduce the number of overloads and also to avoid any potential for ambiguity. - -An open design question is whether all algorithms should take projections, or only the higher-order algorithms. In our current design, all algorithms take projections. This makes it trivial to, say, copy all the keys of a map by using the standard `copy` algorithm with `&pair::first` as the projection. - -### Projections versus Range Transform View - -In a sense, the use of a projection parameter to an algorithm is similar to applying a transform view directly to a range. For example, calling `std::find` with a projection is similar to applying a transform to a range and calling without the projection: - - auto it = std::find( a, 42, &employee::age ); - - auto a2 = a | view::transform( &employee::age ); - auto it2 = std::find( a2, 42 ); - -Aside from the extra verbosity of the view-based solution, there are two meaningful differences: (1) The type of the resulting iterator is different; `*it` refers to an `employee` whereas `*it2` refers to an `int`. And (2) if the transform function returns an rvalue, then the transformed view cannot model a forward sequence due to the current requirements on the ForwardIterator concept. The result of applying a transform view is an Input range unless the transform function returns an lvalue. The projection-based interface suffers no such degradation of the iterator category. (Note: if the concepts in [N3351][8][@n3351] are adopted, this argument is no longer valid.) For those reasons, range transform adapters are not a replacement for projection arguments to algorithms. - -See [Algorithm Implementation with Projections](#algorithm-implementation-with-projections) for a discussion of how projections affect the implementation. - -The addition of projection arguments to the algorithms is pure extension. - -Appendix 6: Implementation Notes -===== - -## On Distinguishing Ranges from Non-Range Iterables - -The design of the range library depends on the ability to tell apart Ranges from Iterables. Ranges are lightweight objects that refer to elements they do not own. As a result, they can guarantee O(1) copyability and assignability. Iterables, on the other hand, may or may not own their elements, and so cannot guarantee anything about the algorithmic complexity of their copy and assign operations. Indeed, an Iterable may not be copyable at all: it may be a native array or a `vector` of move-only types. - -But how to tell Ranges apart from Iterables? After all, whether an Iterable owns its elements or not is largely a semantic difference with no purely syntactic way to differentiate. Well, that's almost true... - -It turns out that there is a reasonably good heuristic we can use to tell Iterables and Ranges apart. Imagine that we have some Iterable type `T` that is either a container like `list`, `vector`, or a native array; or else it's a Range like `pair`. Then we can imagine taking iterators to `T` and `const T`, dereferencing them, and comparing the resulting reference types. The following table gives the results we might expect to find. - -Expression | Container | Range ---------------------------------|-----------------------|----------------------- -`*begin(declval())` | `value_type &` | `[const] value_type &` -`*begin(declval())` | `const value_type &` | `[const] value_type &` - -Notice how containers and ranges differ in the way a top-level cv-qualifier affects the reference type. Since a range is a proxy to elements stored elsewhere, a top-level `const` qualification on the *range* object typically has no effect at all on its iterator's reference type. But that's not true for a container that owns its elements. - -We can use this to build an `is_range` traits that gives a pretty good guess whether an Iterable type is a range or not. This trait can be used to define the Range concept. Obviously since it's a trait, users are free to specialize it if the trait guesses wrong. - -Some people want their range types to behave like containers with respect to the handling of top-level `const`; that is, they would like their ranges to be designed such that if the range object is `const`, the range's elements cannot be mutated through it. There is nothing about the Range concepts that precludes that design, but it does require the developer of such a range to specialize the `is_range` trait. If anything, the default behavior of the trait can be seen as gentle encouragement to handle top-level `const` in a way that is consistent with ranges' nature as a lightweight proxy. - -As an added convenience, we can provide a class, `range_base`, from which users can create a derived type as another way of opting in to "range-ness". The `is_range` trait can test for derivation from `range_base` as an extra test. This would save users the trouble of opening the `std` namespace to specialize the `is_range` trait on the rare occasions that that is necessary. - -The `is_range` trait will also need to be specialized for immutable containers, for which both the mutable and const iterators have the same reference type. A good example is `std::set`. - -If the `is_range` trait erroneously reports `false` for a type that is actually a range, then the library errs on the side of caution and will prevent the user from using rvalues of that type in range adaptor pipelines. If, on the other hand, the `is_range` trait gets the answer wrong for a type that is actually a container, the container ends up being copied or moved into range adaptors. This is a performance bug, and it may give surprising results at runtime if the original container doesn't get mutated when the user thinks it should. It's not a memory error, though. - -## Algorithm Implementation with Projections - -Rather than requiring additional overloads, the addition of projection arguments has very little cost to library implementers. The use of function template default parameters obviates the need for overloads. For instance, `find` can be defined as: - - template> Proj = identity> - requires EqualityComparable && - EqualityComparable>> - I find(I first, S last, V const & val, Proj proj = Proj{}) - { - /* ... */ - } - -## Algorithms That Need An End Iterator - -Some algorithms need to know the real physical end of the input sequence so that the sequence can be traversed backwards, like `reverse`. In those cases, it's helpful to have an algorithm `advance_to` that takes an iterator and a sentinel and returns a real end iterator. `advance_to` looks like this: - - - template - requires IteratorRange - I advance_to( I i, S s ) - { - while(i != s) - ++i; - return i; - } - - template - requires SizedIteratorRange - I advance_to( I i, S s ) - { - advance( i, s - i ); - return i; - } - - template - I advance_to( I, I s ) - { - return s; - } - -When the sentinel is actually an iterator, we already know where the end is so we can just return it. Notice how we handle SizedIteratorRanges specially and dispatch to `advance` with a count. [Appendix 4](#appendix-4-on-counted-ranges-and-efficiency) shows how `advance` is optimized for counted iterators. By dispatching to `advance` when we can, we make `advance_to` faster for counted iterators, too. - -With `advance_to` we can implement `reverse` generically as: - - template - requires EqualityComparable && Permutable - I reverse( I first, S last_ ) - { - I last = advance_to( first, last_ ), end = last; - while( first != last ) - { - if( first == --last ) - break; - iter_swap( first, last ); - ++first; - } - return end; - } - -Since this algorithm necessarily computes the end of the sequence if it isn't known already, we return it. diff --git a/vendor/range-v3/range-v3/doc/std/header.html b/vendor/range-v3/range-v3/doc/std/header.html deleted file mode 100644 index 9412972f..00000000 --- a/vendor/range-v3/range-v3/doc/std/header.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - -
Document number: - D4128=yy-nnnn -
Date:2014-10-10
Project:Programming Language C++, Library Working Group
Reply-to: - Eric Niebler <eniebler@boost.org>,
- Sean Parent <sparent@adobe.com>,
- Andrew Sutton <andrew.n.sutton@gmail.com> -
diff --git a/vendor/range-v3/range-v3/doc/std/pandoc-template.html b/vendor/range-v3/range-v3/doc/std/pandoc-template.html deleted file mode 100644 index 5a36b3b6..00000000 --- a/vendor/range-v3/range-v3/doc/std/pandoc-template.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - -$for(author-meta)$ - -$endfor$ -$if(date-meta)$ - -$endif$ - $if(title-prefix)$$title-prefix$ - $endif$$pagetitle$ - -$if(quotes)$ - -$endif$ -$if(highlighting-css)$ - -$endif$ -$for(css)$ - -$endfor$ -$if(math)$ - $math$ -$endif$ -$for(header-includes)$ - $header-includes$ -$endfor$ - - -$for(include-before)$ -$include-before$ -$endfor$ -$if(title)$ -

-

$title$

-$if(subtitle)$ -

$subtitle$

-$endif$ -$for(author)$ -

$author$

-$endfor$ -$if(date)$ -

$date$

-$endif$ -
-$endif$ -
-

“A beginning is the time for taking the most delicate care that the balances are correct.”

-

- – Frank Herbert, Dune -

-
-$if(toc)$ -
-$toc$ -
-$endif$ -$body$ -$for(include-after)$ -$include-after$ -$endfor$ - - diff --git a/vendor/range-v3/range-v3/doc/std/show.cmd b/vendor/range-v3/range-v3/doc/std/show.cmd deleted file mode 100644 index 521532e6..00000000 --- a/vendor/range-v3/range-v3/doc/std/show.cmd +++ /dev/null @@ -1 +0,0 @@ -pandoc -f markdown_github+yaml_metadata_block+citations -t html -o D4128.html --filter pandoc-citeproc --csl=acm-sig-proceedings.csl --number-sections --toc -s -S --template=pandoc-template --include-before-body=header.html D4128.md diff --git a/vendor/range-v3/range-v3/example/BUCK b/vendor/range-v3/range-v3/example/BUCK deleted file mode 100644 index 8c9d9f8c..00000000 --- a/vendor/range-v3/range-v3/example/BUCK +++ /dev/null @@ -1,155 +0,0 @@ -cxx_binary( - name = 'comprehensions', - srcs = [ - 'comprehensions.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'count_if', - srcs = [ - 'count_if.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'count', - srcs = [ - 'count.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'find', - srcs = [ - 'find.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'for_each_assoc', - srcs = [ - 'for_each_assoc.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'for_each_sequence', - srcs = [ - 'for_each_sequence.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'hello', - srcs = [ - 'hello.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'is_sorted', - srcs = [ - 'is_sorted.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'filter_transform', - srcs = [ - 'filter_transform.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'accumulate_ints', - srcs = [ - 'accumulate_ints.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'comprehension_conversion', - srcs = [ - 'comprehension_conversion.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) - -cxx_binary( - name = 'sort_unique', - srcs = [ - 'sort_unique.cpp', - ], - deps = [ - '//:range-v3', - ], - compiler_flags = [ - '-std=c++14', - ], -) diff --git a/vendor/range-v3/range-v3/example/CMakeLists.txt b/vendor/range-v3/range-v3/example/CMakeLists.txt deleted file mode 100644 index 32978f97..00000000 --- a/vendor/range-v3/range-v3/example/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -set(CMAKE_FOLDER "example") - -# examples use a less draconian set of compiler warnings -ranges_append_flag(RANGES_HAS_WNO_MISSING_BRACES -Wno-missing-braces) -ranges_append_flag(RANGES_HAS_WNO_SHORTEN_64_TO_32 -Wno-shorten-64-to-32) -ranges_append_flag(RANGES_HAS_WNO_GLOBAL_CONSTRUCTORS -Wno-global-constructors) - -rv3_add_test(example.comprehensions comprehensions comprehensions.cpp) -rv3_add_test(example.hello hello hello.cpp) -rv3_add_test(example.count count count.cpp) -rv3_add_test(example.count_if count_if count_if.cpp) -rv3_add_test(example.any_all_none_of any_all_none_of any_all_none_of.cpp) -rv3_add_test(example.for_each_sequence for_each_sequence for_each_sequence.cpp) -rv3_add_test(example.for_each_assoc for_each_assoc for_each_assoc.cpp) -rv3_add_test(example.is_sorted is_sorted is_sorted.cpp) -rv3_add_test(example.find find find.cpp) -rv3_add_test(example.filter_transform filter_transform filter_transform.cpp) -rv3_add_test(example.accumulate_ints accumulate_ints accumulate_ints.cpp) -rv3_add_test(example.comprehension_conversion comprehension_conversion comprehension_conversion.cpp) -rv3_add_test(example.sort_unique sort_unique sort_unique.cpp) - -# Guarded with a variable because the calendar example causes gcc to puke. -if(RANGES_BUILD_CALENDAR_EXAMPLE) - set(Boost_USE_STATIC_LIBS ON) - set(Boost_USE_MULTITHREADED OFF) - set(Boost_USE_STATIC_RUNTIME OFF) - find_package(Boost 1.59.0 COMPONENTS date_time program_options) - - if (Boost_FOUND) - add_executable(calendar calendar.cpp) - target_include_directories(calendar SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) - target_link_libraries(calendar range-v3 ${Boost_LIBRARIES}) - message(STATUS "boost: ${Boost_LIBRARY_DIRS}") - target_compile_definitions(calendar PRIVATE BOOST_NO_AUTO_PTR) - target_compile_options(calendar PRIVATE -std=gnu++14) - set_target_properties(calendar PROPERTIES LINK_FLAGS "-L${Boost_LIBRARY_DIRS}") - endif() -endif() diff --git a/vendor/range-v3/range-v3/example/accumulate_ints.cpp b/vendor/range-v3/range-v3/example/accumulate_ints.cpp deleted file mode 100644 index 65769239..00000000 --- a/vendor/range-v3/range-v3/example/accumulate_ints.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Range v3 library -// -// Copyright Eric Niebler 2019 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[accumulate_ints] -// Sums the first ten squares and prints them, using views::ints to generate -// and infinite range of integers, views::transform to square them, views::take -// to drop all but the first 10, and accumulate to sum them. - -#include -#include - -#include -#include -#include -#include -using std::cout; - -int main() -{ - using namespace ranges; - int sum = accumulate(views::ints(1, unreachable) | views::transform([](int i) { - return i * i; - }) | views::take(10), - 0); - // prints: 385 - cout << sum << '\n'; -} -///[accumulate_ints] diff --git a/vendor/range-v3/range-v3/example/any_all_none_of.cpp b/vendor/range-v3/range-v3/example/any_all_none_of.cpp deleted file mode 100644 index edf5be57..00000000 --- a/vendor/range-v3/range-v3/example/any_all_none_of.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Range v3 library -// -// Copyright Jeff Garland 2017 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -//[any_all_none_of]] -// Demonstrates any_of, all_of, none_of -// output -// vector: [6,2,3,4,5,6] -// vector any_of is_six: true -// vector all_of is_six: false -// vector none_of is_six: false - -#include -#include -#include -#include -#include - -#include -#include -using std::cout; - -auto is_six = [](int i) { return i == 6; }; - -int -main() -{ - std::vector v{6, 2, 3, 4, 5, 6}; - cout << std::boolalpha; - cout << "vector: " << ranges::views::all(v) << '\n'; - - cout << "vector any_of is_six: " << ranges::any_of(v, is_six) << '\n'; - cout << "vector all_of is_six: " << ranges::all_of(v, is_six) << '\n'; - cout << "vector none_of is_six: " << ranges::none_of(v, is_six) << '\n'; -} -//[any_all_none_of]] diff --git a/vendor/range-v3/range-v3/example/calendar.cpp b/vendor/range-v3/range-v3/example/calendar.cpp deleted file mode 100644 index 077103c4..00000000 --- a/vendor/range-v3/range-v3/example/calendar.cpp +++ /dev/null @@ -1,441 +0,0 @@ -// Range v3 library -// -// Copyright Eric Niebler 2013-present -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -#include - -#if RANGES_CXX_RETURN_TYPE_DEDUCTION >= RANGES_CXX_RETURN_TYPE_DEDUCTION_14 && \ - RANGES_CXX_GENERIC_LAMBDAS >= RANGES_CXX_GENERIC_LAMBDAS_14 - -///[calendar] - -// Usage: -// calendar 2015 -// -// Output: -/* - January February March - 1 2 3 1 2 3 4 5 6 7 1 2 3 4 5 6 7 - 4 5 6 7 8 9 10 8 9 10 11 12 13 14 8 9 10 11 12 13 14 - 11 12 13 14 15 16 17 15 16 17 18 19 20 21 15 16 17 18 19 20 21 - 18 19 20 21 22 23 24 22 23 24 25 26 27 28 22 23 24 25 26 27 28 - 25 26 27 28 29 30 31 29 30 31 - - April May June - 1 2 3 4 1 2 1 2 3 4 5 6 - 5 6 7 8 9 10 11 3 4 5 6 7 8 9 7 8 9 10 11 12 13 - 12 13 14 15 16 17 18 10 11 12 13 14 15 16 14 15 16 17 18 19 20 - 19 20 21 22 23 24 25 17 18 19 20 21 22 23 21 22 23 24 25 26 27 - 26 27 28 29 30 24 25 26 27 28 29 30 28 29 30 - 31 - July August September - 1 2 3 4 1 1 2 3 4 5 - 5 6 7 8 9 10 11 2 3 4 5 6 7 8 6 7 8 9 10 11 12 - 12 13 14 15 16 17 18 9 10 11 12 13 14 15 13 14 15 16 17 18 19 - 19 20 21 22 23 24 25 16 17 18 19 20 21 22 20 21 22 23 24 25 26 - 26 27 28 29 30 31 23 24 25 26 27 28 29 27 28 29 30 - 30 31 - October November December - 1 2 3 1 2 3 4 5 6 7 1 2 3 4 5 - 4 5 6 7 8 9 10 8 9 10 11 12 13 14 6 7 8 9 10 11 12 - 11 12 13 14 15 16 17 15 16 17 18 19 20 21 13 14 15 16 17 18 19 - 18 19 20 21 22 23 24 22 23 24 25 26 27 28 20 21 22 23 24 25 26 - 25 26 27 28 29 30 31 29 30 27 28 29 30 31 -// */ - -// Credits: -// Thanks to H. S. Teoh for the article that served as the -// inspiration for this example: -// -// Thanks to github's Arzar for bringing date::week_number -// to my attention. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace po = boost::program_options; -namespace greg = boost::gregorian; -using date = greg::date; -using day = greg::date_duration; -using namespace ranges; - -namespace boost -{ - namespace gregorian - { - date &operator++(date &d) - { - return d = d + day(1); - } - date operator++(date &d, int) - { - return ++d - day(1); - } - } -} -namespace ranges -{ - template<> - struct incrementable_traits - { - using difference_type = date::duration_type::duration_rep::int_type; - }; -} -CPP_assert(incrementable); - -auto -dates(unsigned short start, unsigned short stop) -{ - return views::iota(date{start, greg::Jan, 1}, date{stop, greg::Jan, 1}); -} - -auto -dates_from(unsigned short year) -{ - return views::iota(date{year, greg::Jan, 1}); -} - -auto -by_month() -{ - return views::group_by( - [](date a, date b) { return a.month() == b.month(); }); -} - -auto -by_week() -{ - return views::group_by([](date a, date b) { - // ++a because week_number is Mon-Sun and we want Sun-Sat - return (++a).week_number() == (++b).week_number(); - }); -} - -std::string -format_day(date d) -{ - return boost::str(boost::format("%|3|") % d.day()); -} - -// In: range>: month grouped by weeks. -// Out: range: month with formatted weeks. -auto -format_weeks() -{ - return views::transform([](/*range*/ auto week) { - return boost::str(boost::format("%1%%2%%|22t|") % - std::string(front(week).day_of_week() * 3u, ' ') % - (week | views::transform(format_day) | actions::join)); - }); -} - -// Return a formatted string with the title of the month -// corresponding to a date. -std::string -month_title(date d) -{ - return boost::str(boost::format("%|=22|") % d.month().as_long_string()); -} - -// In: range>: year of months of days -// Out: range>: year of months of formatted wks -auto -layout_months() -{ - return views::transform([](/*range*/ auto month) { - auto week_count = - static_cast(distance(month | by_week())); - return views::concat( - views::single(month_title(front(month))), - month | by_week() | format_weeks(), - views::repeat_n(std::string(22, ' '), 6 - week_count)); - }); -} - -namespace cal_example -{ - - // In: range - // Out: range>, where each inner range has $n$ elements. - // The last range may have fewer. - template - class chunk_view : public view_adaptor, Rng> - { - CPP_assert(forward_range); - ranges::range_difference_t n_; - friend range_access; - class adaptor; - adaptor begin_adaptor() - { - return adaptor{n_, ranges::end(this->base())}; - } - - public: - chunk_view() = default; - chunk_view(Rng rng, ranges::range_difference_t n) - : chunk_view::view_adaptor(std::move(rng)) - , n_(n) - {} - }; - - template - class chunk_view::adaptor : public adaptor_base - { - ranges::range_difference_t n_; - sentinel_t end_; - - public: - adaptor() = default; - adaptor(ranges::range_difference_t n, sentinel_t last) - : n_(n) - , end_(last) - {} - auto read(iterator_t it) const - { - return views::take(make_subrange(std::move(it), end_), n_); - } - void next(iterator_t &it) - { - ranges::advance(it, n_, end_); - } - void prev() = delete; - void distance_to() = delete; - }; - -} // namespace cal_example - -// In: range -// Out: range>, where each inner range has $n$ elements. -// The last range may have fewer. -auto -chunk(std::size_t n) -{ - return make_pipeable([=](auto &&rng) { - using Rng = decltype(rng); - return cal_example::chunk_view>{ - views::all(std::forward(rng)), - static_cast>(n)}; - }); -} - -// Flattens a range of ranges by iterating the inner -// ranges in round-robin fashion. -template -class interleave_view : public view_facade> -{ - friend range_access; - std::vector> rngs_; - struct cursor; - cursor begin_cursor() - { - return {0, &rngs_, views::transform(rngs_, ranges::begin) | to}; - } - -public: - interleave_view() = default; - explicit interleave_view(Rngs rngs) - : rngs_(std::move(rngs) | to) - {} -}; - -template -struct interleave_view::cursor -{ - std::size_t n_; - std::vector> *rngs_; - std::vector>> its_; - decltype(auto) read() const - { - return *its_[n_]; - } - void next() - { - if(0 == ((++n_) %= its_.size())) - for_each(its_, [](auto &it) { ++it; }); - } - bool equal(default_sentinel_t) const - { - if(n_ != 0) - return false; - auto ends = *rngs_ | views::transform(ranges::end); - return its_.end() != std::mismatch( - its_.begin(), its_.end(), ends.begin(), std::not_equal_to<>{}).first; - } - CPP_member - auto equal(cursor const& that) const -> CPP_ret(bool)( - requires forward_range>) - { - return n_ == that.n_ && its_ == that.its_; - } -}; - -// In: range> -// Out: range, flattened by walking the ranges -// round-robin fashion. -auto -interleave() -{ - return make_pipeable([](auto &&rngs) { - using Rngs = decltype(rngs); - return interleave_view>( - views::all(std::forward(rngs))); - }); -} - -// In: range> -// Out: range>, transposing the rows and columns. -auto -transpose() -{ - return make_pipeable([](auto &&rngs) { - using Rngs = decltype(rngs); - CPP_assert(forward_range); - return std::forward(rngs) - | interleave() - | chunk(static_cast(distance(rngs))); - }); -} - -// In: range>> -// Out: range>>, transposing months. -auto -transpose_months() -{ - return views::transform( - [](/*range>*/ auto rng) { return rng | transpose(); }); -} - -// In: range> -// Out: range, joining the strings of the inner ranges -auto -join_months() -{ - return views::transform( - [](/*range*/ auto rng) { return actions::join(rng); }); -} - -// In: range -// Out: range, lines of formatted output -auto -format_calendar(std::size_t months_per_line) -{ - return make_pipeable([=](auto &&rng) { - using Rng = decltype(rng); - return std::forward(rng) - // Group the dates by month: - | by_month() - // Format the month into a range of strings: - | layout_months() - // Group the months that belong side-by-side: - | chunk(months_per_line) - // Transpose the rows and columns of the size-by-side months: - | transpose_months() - // Ungroup the side-by-side months: - | views::join - // Join the strings of the transposed months: - | join_months(); - }); -} - -int -main(int argc, char *argv[]) try -{ - // Declare the supported options. - po::options_description desc("Allowed options"); - desc.add_options()("help", "produce help message")( - "start", po::value(), "Year to start")( - "stop", po::value(), "Year to stop")( - "per-line", - po::value()->default_value(3u), - "Nbr of months per line"); - - po::positional_options_description p; - p.add("start", 1).add("stop", 1); - - po::variables_map vm; - po::store( - po::command_line_parser(argc, argv).options(desc).positional(p).run(), - vm); - po::notify(vm); - - if(vm.count("help") || 1 != vm.count("start")) - { - std::cerr << desc << '\n'; - return 1; - } - - auto const start = vm["start"].as(); - auto const stop = 0 == vm.count("stop") - ? (unsigned short)(start + 1) - : vm["stop"].as() == "never" - ? (unsigned short)-1 - : boost::lexical_cast( - vm["stop"].as()); - auto const months_per_line = vm["per-line"].as(); - - if(stop != (unsigned short)-1 && stop <= start) - { - std::cerr << "ERROR: The stop year must be larger than the start" - << '\n'; - return 1; - } - - if((unsigned short)-1 != stop) - { - copy(dates(start, stop) | format_calendar(months_per_line), - ostream_iterator<>(std::cout, "\n")); - } - else - { - copy(dates_from(start) | format_calendar(months_per_line), - ostream_iterator<>(std::cout, "\n")); - } -} -catch(std::exception &e) -{ - std::cerr << "ERROR: Unhandled exception\n"; - std::cerr << " what(): " << e.what(); - return 1; -} -///[calendar] - -#else -#pragma message( \ - "calendar requires C++14 return type deduction and generic lambdas") -int -main() -{} -#endif diff --git a/vendor/range-v3/range-v3/example/comprehension_conversion.cpp b/vendor/range-v3/range-v3/example/comprehension_conversion.cpp deleted file mode 100644 index 8b327ae4..00000000 --- a/vendor/range-v3/range-v3/example/comprehension_conversion.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Range v3 library -// -// Copyright Eric Niebler 2019 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[comprehension_conversion] -// Use a range comprehension (views::for_each) to construct a custom range, and -// then convert it to a std::vector. - -#include -#include - -#include -#include -#include -#include -using std::cout; - -int main() -{ - using namespace ranges; - auto vi = views::for_each(views::ints(1, 6), - [](int i) { return yield_from(views::repeat_n(i, i)); }) | - to(); - // prints: [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5] - cout << views::all(vi) << '\n'; -} -///[comprehension_conversion] diff --git a/vendor/range-v3/range-v3/example/comprehensions.cpp b/vendor/range-v3/range-v3/example/comprehensions.cpp deleted file mode 100644 index 8bdea0e7..00000000 --- a/vendor/range-v3/range-v3/example/comprehensions.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// Range v3 library -// -// Copyright Eric Niebler 2013-present -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -#include -#include -#include - -using namespace ranges; - -int -main() -{ - // Define an infinite range containing all the Pythagorean triples: - auto triples = views::for_each(views::iota(1), [](int z) { - return views::for_each(views::iota(1, z + 1), [=](int x) { - return views::for_each(views::iota(x, z + 1), [=](int y) { - return yield_if(x * x + y * y == z * z, - std::make_tuple(x, y, z)); - }); - }); - }); - - //// This alternate syntax also works: - // auto triples = iota(1) >>= [] (int z) { return - // iota(1, z+1) >>= [=](int x) { return - // iota(x, z+1) >>= [=](int y) { return - // yield_if(x*x + y*y == z*z, - // std::make_tuple(x, y, z)); };}; }; - - // Display the first 100 triples - RANGES_FOR(auto triple, triples | views::take(100)) - { - std::cout << '(' << std::get<0>(triple) << ',' << std::get<1>(triple) - << ',' << std::get<2>(triple) << ')' << '\n'; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Benchmark Code -//////////////////////////////////////////////////////////////////////////////////////////////////// - -class timer -{ -private: - std::chrono::high_resolution_clock::time_point start_; - -public: - timer() - { - reset(); - } - void reset() - { - start_ = std::chrono::high_resolution_clock::now(); - } - std::chrono::milliseconds elapsed() const - { - return std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - start_); - } - friend std::ostream &operator<<(std::ostream &sout, timer const &t) - { - return sout << t.elapsed().count() << "ms"; - } -}; - -void -benchmark() -{ - // Define an infinite range containing all the Pythagorean triples: - auto triples = views::for_each(views::iota(1), [](int z) { - return views::for_each(views::iota(1, z + 1), [=](int x) { - return views::for_each(views::iota(x, z + 1), [=](int y) { - return yield_if(x * x + y * y == z * z, - std::make_tuple(x, y, z)); - }); - }); - }); - - static constexpr int max_triples = 3000; - - timer t; - int result = 0; - RANGES_FOR(auto triple, triples | views::take(max_triples)) - { - int i, j, k; - std::tie(i, j, k) = triple; - result += (i + j + k); - } - std::cout << t << '\n'; - std::cout << result << '\n'; - - result = 0; - int found = 0; - t.reset(); - for(int z = 1;; ++z) - { - for(int x = 1; x <= z; ++x) - { - for(int y = x; y <= z; ++y) - { - if(x * x + y * y == z * z) - { - result += (x + y + z); - ++found; - if(found == max_triples) - goto done; - } - } - } - } -done: - std::cout << t << '\n'; - std::cout << result << '\n'; -} diff --git a/vendor/range-v3/range-v3/example/count.cpp b/vendor/range-v3/range-v3/example/count.cpp deleted file mode 100644 index ffc17d87..00000000 --- a/vendor/range-v3/range-v3/example/count.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Range v3 library -// -// Copyright Jeff Garland 2017 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[count] -// This example demonstrates counting the number of -// elements that match a given value. -// output... -// vector: 2 -// array: 2 - -#include -#include // specific includes -#include -using std::cout; - -int -main() -{ - std::vector v{6, 2, 3, 4, 5, 6}; - // note the count return is a numeric type - // like int or long -- auto below make sure - // it matches the implementation - auto c = ranges::count(v, 6); - cout << "vector: " << c << '\n'; - - std::array a{6, 2, 3, 4, 5, 6}; - c = ranges::count(a, 6); - cout << "array: " << c << '\n'; -} - -///[count] diff --git a/vendor/range-v3/range-v3/example/count_if.cpp b/vendor/range-v3/range-v3/example/count_if.cpp deleted file mode 100644 index 0a9e3ebe..00000000 --- a/vendor/range-v3/range-v3/example/count_if.cpp +++ /dev/null @@ -1,39 +0,0 @@ - -// Range v3 library -// -// Copyright Jeff Garland 2017 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[count_if] -// This example counts element of a range that match a supplied predicate. -// output -// vector: 2 -// array: 2 - -#include -#include -#include // specific includes -#include -using std::cout; - -auto is_six = [](int i) -> bool { return i == 6; }; - -int -main() -{ - std::vector v{6, 2, 3, 4, 5, 6}; - auto c = ranges::count_if(v, is_six); - cout << "vector: " << c << '\n'; // 2 - - std::array a{6, 2, 3, 4, 5, 6}; - c = ranges::count_if(a, is_six); - cout << "array: " << c << '\n'; // 2 -} -///[count_if] diff --git a/vendor/range-v3/range-v3/example/filter_transform.cpp b/vendor/range-v3/range-v3/example/filter_transform.cpp deleted file mode 100644 index f8783a62..00000000 --- a/vendor/range-v3/range-v3/example/filter_transform.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Range v3 library -// -// Copyright Eric Niebler 2019 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[filter_transform] -// This example demonstrates filtering and transforming a range on the -// fly with view adaptors. - -#include -#include -#include - -#include -#include -using std::cout; - -int main() -{ - std::vector const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - using namespace ranges; - auto rng = vi | views::filter([](int i) { return i % 2 == 0; }) | - views::transform([](int i) { return std::to_string(i); }); - // prints: [2,4,6,8,10] - cout << rng << '\n'; -} -///[filter_transform] diff --git a/vendor/range-v3/range-v3/example/find.cpp b/vendor/range-v3/range-v3/example/find.cpp deleted file mode 100644 index 060f5ef2..00000000 --- a/vendor/range-v3/range-v3/example/find.cpp +++ /dev/null @@ -1,118 +0,0 @@ -// Range v3 library -// -// Copyright Jeff Garland 2017 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[find] -// vector: *i: 6 -// didn't find 10 -// *i: 6 -// *i: 2 -// *i after ++ (2 expected): 2 -// array: *i: 6 -// list: *i: 6 -// fwd_list: *i: 4 -// deque: *i: 6 - -#include -#include -#include -#include -#include -#include -#include -using std::cout; - -auto is_six = [](int i) -> bool { return i == 6; }; - -int -main() -{ - cout << "vector: "; - - std::vector v{6, 2, 6, 4, 6, 1}; - { - auto i = ranges::find(v, 6); // 1 2 3 4 5 6 - cout << "*i: " << *i << '\n'; - } - { - auto i = ranges::find(v, 10); // 1 2 3 4 5 6 - if(i == ranges::end(v)) - { - cout << "didn't find 10\n"; - } - } - { - auto i = ranges::find_if(v, is_six); - if(i != ranges::end(v)) - { - cout << "*i: " << *i << '\n'; - } - } - { - auto i = ranges::find_if_not(v, is_six); - if(i != ranges::end(v)) - { - cout << "*i: " << *i << '\n'; - } - } - { - auto i = ranges::find(v, 6); - i++; - if(i != ranges::end(v)) - { - cout << "*i after ++ (2 expected): " << *i; - } - } - - cout << "\narray: "; - - std::array a{6, 2, 3, 4, 5, 1}; - { - auto i = ranges::find(a, 6); - if(i != ranges::end(a)) - { - cout << "*i: " << *i; - } - } - cout << "\nlist: "; - - std::list li{6, 2, 3, 4, 5, 1}; - { - auto i = ranges::find(li, 6); - if(i != ranges::end(li)) - { - cout << "*i: " << *i; - } - } - - cout << "\nfwd_list: "; - - std::forward_list fl{6, 2, 3, 4, 5, 1}; - { - auto i = ranges::find(fl, 4); - if(i != ranges::end(fl)) - { - cout << "*i: " << *i; - } - } - cout << "\ndeque: "; - - std::deque d{6, 2, 3, 4, 5, 1}; - { - auto i = ranges::find(d, 6); - if(i != ranges::end(d)) - { - cout << "*i: " << *i; - } - } - cout << '\n'; -} -///[find] diff --git a/vendor/range-v3/range-v3/example/for_each_assoc.cpp b/vendor/range-v3/range-v3/example/for_each_assoc.cpp deleted file mode 100644 index 3c1dfbbc..00000000 --- a/vendor/range-v3/range-v3/example/for_each_assoc.cpp +++ /dev/null @@ -1,58 +0,0 @@ - -// Range v3 library -// -// Copyright Jeff Garland 2017 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[for_each_assoc] -// for_each with associative containers -// output -// set: 1 2 3 4 5 6 -// map: one:1 three:3 two:2 -// unordered_map: three:3 one:1 two:2 -// unordered_set: 6 5 4 3 2 1 - -#include -#include -#include -#include -#include -#include -#include -using std::cout; -using std::string; - -auto print = [](int i) { cout << i << ' '; }; -// must take a pair for map types -auto printm = [](std::pair p) { - cout << p.first << ":" << p.second << ' '; -}; - -int -main() -{ - cout << "set: "; - std::set si{1, 2, 3, 4, 5, 6}; - ranges::for_each(si, print); - - cout << "\nmap: "; - std::map msi{{"one", 1}, {"two", 2}, {"three", 3}}; - ranges::for_each(msi, printm); - - cout << "\nunordered map: "; - std::unordered_map umsi{{"one", 1}, {"two", 2}, {"three", 3}}; - ranges::for_each(umsi, printm); - - cout << "\nunordered set: "; - std::unordered_set usi{1, 2, 3, 4, 5, 6}; - ranges::for_each(usi, print); - cout << '\n'; -} -///[for_each_assoc] diff --git a/vendor/range-v3/range-v3/example/for_each_sequence.cpp b/vendor/range-v3/range-v3/example/for_each_sequence.cpp deleted file mode 100644 index 99c94f48..00000000 --- a/vendor/range-v3/range-v3/example/for_each_sequence.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Range v3 library -// -// Copyright Jeff Garland 2017 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[for_each_sequence] -// Use the for_each to print from various containers -// output -// vector: 1 2 3 4 5 6 -// array: 1 2 3 4 5 6 -// list: 1 2 3 4 5 6 -// fwd_list: 1 2 3 4 5 6 -// deque: 1 2 3 4 5 6 - -#include -#include -#include -#include -#include -#include -#include // specific includes -#include -#include -using std::cout; - -auto print = [](int i) { cout << i << ' '; }; - -int -main() -{ - cout << "vector: "; - std::vector v{1, 2, 3, 4, 5, 6}; - ranges::for_each(v, print); // 1 2 3 4 5 6 - - cout << "\narray: "; - std::array a{1, 2, 3, 4, 5, 6}; - ranges::for_each(a, print); - - cout << "\nlist: "; - std::list ll{1, 2, 3, 4, 5, 6}; - ranges::for_each(ll, print); - - cout << "\nfwd_list: "; - std::forward_list fl{1, 2, 3, 4, 5, 6}; - ranges::for_each(fl, print); - - cout << "\ndeque: "; - std::deque d{1, 2, 3, 4, 5, 6}; - ranges::for_each(d, print); - cout << '\n'; -} -///[for_each_sequence] diff --git a/vendor/range-v3/range-v3/example/hello.cpp b/vendor/range-v3/range-v3/example/hello.cpp deleted file mode 100644 index 2f551ab6..00000000 --- a/vendor/range-v3/range-v3/example/hello.cpp +++ /dev/null @@ -1,29 +0,0 @@ - -// Range v3 library -// -// Copyright Jeff Garland 2017 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[hello] -#include -#include // get everything -#include -using std::cout; - -int -main() -{ - std::string s{"hello"}; - - // output: h e l l o - ranges::for_each(s, [](char c) { cout << c << ' '; }); - cout << '\n'; -} -///[hello] diff --git a/vendor/range-v3/range-v3/example/is_sorted.cpp b/vendor/range-v3/range-v3/example/is_sorted.cpp deleted file mode 100644 index d479d4bf..00000000 --- a/vendor/range-v3/range-v3/example/is_sorted.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Range v3 library -// -// Copyright Jeff Garland 2017 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[is_sorted] -// Check if a container is sorted -// output -// vector: true -// array: false - -#include -#include -#include // specific includes -#include -using std::cout; - -int -main() -{ - cout << std::boolalpha; - std::vector v{1, 2, 3, 4, 5, 6}; - cout << "vector: " << ranges::is_sorted(v) << '\n'; - - std::array a{6, 2, 3, 4, 5, 6}; - cout << "array: " << ranges::is_sorted(a) << '\n'; -} -///[is_sorted] diff --git a/vendor/range-v3/range-v3/example/sort_unique.cpp b/vendor/range-v3/range-v3/example/sort_unique.cpp deleted file mode 100644 index 4c924d9e..00000000 --- a/vendor/range-v3/range-v3/example/sort_unique.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Range v3 library -// -// Copyright Eric Niebler 2019 -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 -// - -///[sort_unique] -// Remove all non-unique elements from a container. - -#include -#include - -#include -#include -#include -using std::cout; - -int main() -{ - std::vector vi{9, 4, 5, 2, 9, 1, 0, 2, 6, 7, 4, 5, 6, 5, 9, 2, 7, - 1, 4, 5, 3, 8, 5, 0, 2, 9, 3, 7, 5, 7, 5, 5, 6, 1, - 4, 3, 1, 8, 4, 0, 7, 8, 8, 2, 6, 5, 3, 4, 5}; - using namespace ranges; - vi |= actions::sort | actions::unique; - // prints: [0,1,2,3,4,5,6,7,8,9] - cout << views::all(vi) << '\n'; -} -///[sort_unique] diff --git a/vendor/range-v3/range-v3/include/concepts/concepts.hpp b/vendor/range-v3/range-v3/include/concepts/concepts.hpp deleted file mode 100644 index 72ac3960..00000000 --- a/vendor/range-v3/range-v3/include/concepts/concepts.hpp +++ /dev/null @@ -1,1164 +0,0 @@ -/// \file -// CPP, the Concepts PreProcessor library -// -// Copyright Eric Niebler 2018-present -// Copyright (c) 2018-present, Facebook, Inc. -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. -// -// Project home: https://github.com/ericniebler/range-v3 -// - -#ifndef CPP_CONCEPTS_HPP -#define CPP_CONCEPTS_HPP - -// clang-format off - -#include -#include -#include -#include -#include - -// disable buggy compatibility warning about "requires" and "concept" being -// C++20 keywords. -#if defined(__clang__) || defined(__GNUC__) -#define CPP_PP_IGNORE_CXX2A_COMPAT_BEGIN \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") \ - _Pragma("GCC diagnostic ignored \"-Wpragmas\"") \ - _Pragma("GCC diagnostic ignored \"-Wc++2a-compat\"") \ - _Pragma("GCC diagnostic ignored \"-Wfloat-equal\"") \ - /**/ -#define CPP_PP_IGNORE_CXX2A_COMPAT_END \ - _Pragma("GCC diagnostic pop") -#else -#define CPP_PP_IGNORE_CXX2A_COMPAT_BEGIN -#define CPP_PP_IGNORE_CXX2A_COMPAT_END -#endif - -#if defined(_MSC_VER) && !defined(__clang__) -#define CPP_WORKAROUND_MSVC_779763 // FATAL_UNREACHABLE calling constexpr function via template parameter -#define CPP_WORKAROUND_MSVC_780775 // Incorrect substitution in function template return type -#define CPP_WORKAROUND_MSVC_784772 // Failure to invoke *explicit* bool conversion in a constant expression -#endif - -#if !defined(CPP_CXX_CONCEPTS) -#ifdef CPP_DOXYGEN_INVOKED -#define CPP_CXX_CONCEPTS 201800L -#elif defined(__cpp_concepts) && __cpp_concepts > 0 -// gcc-6 concepts are too buggy to use -#if !defined(__GNUC__) || defined(__clang__) || __GNUC__ >= 7 -#define CPP_CXX_CONCEPTS __cpp_concepts -#else -#define CPP_CXX_CONCEPTS 0L -#endif -#else -#define CPP_CXX_CONCEPTS 0L -#endif -#endif - -CPP_PP_IGNORE_CXX2A_COMPAT_BEGIN - -#define CPP_PP_CHECK(...) CPP_PP_CHECK_N(__VA_ARGS__, 0,) -#define CPP_PP_CHECK_N(x, n, ...) n -#define CPP_PP_PROBE(x) x, 1, -#define CPP_PP_PROBE_N(x, n) x, n, - -// CPP_CXX_VA_OPT -#ifndef CPP_CXX_VA_OPT -#if __cplusplus > 201703L -#define CPP_CXX_VA_OPT_(...) CPP_PP_CHECK(__VA_OPT__(,) 1) -#define CPP_CXX_VA_OPT CPP_CXX_VA_OPT_(~) -#else -#define CPP_CXX_VA_OPT 0 -#endif -#endif // CPP_CXX_VA_OPT - -#define CPP_PP_CAT_(X, ...) X ## __VA_ARGS__ -#define CPP_PP_CAT(X, ...) CPP_PP_CAT_(X, __VA_ARGS__) -#define CPP_PP_CAT2_(X, ...) X ## __VA_ARGS__ -#define CPP_PP_CAT2(X, ...) CPP_PP_CAT2_(X, __VA_ARGS__) - -#define CPP_PP_EVAL(X, ...) X(__VA_ARGS__) -#define CPP_PP_EVAL2(X, ...) X(__VA_ARGS__) - -#define CPP_PP_EXPAND(...) __VA_ARGS__ -#define CPP_PP_EAT(...) - -#define CPP_PP_IS_PAREN(x) CPP_PP_CHECK(CPP_PP_IS_PAREN_PROBE x) -#define CPP_PP_IS_PAREN_PROBE(...) CPP_PP_PROBE(~) - -#define CPP_PP_COUNT(...) \ - CPP_PP_COUNT_(__VA_ARGS__, \ - 50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31, \ - 30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11, \ - 10,9,8,7,6,5,4,3,2,1,) \ - /**/ -#define CPP_PP_COUNT_( \ - _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ - _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \ - _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \ - _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \ - _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, N, ...) \ - N \ - /**/ - -#define CPP_PP_IIF(BIT) CPP_PP_CAT_(CPP_PP_IIF_, BIT) -#define CPP_PP_IIF_0(TRUE, ...) __VA_ARGS__ -#define CPP_PP_IIF_1(TRUE, ...) TRUE - -#define CPP_PP_LPAREN ( - -#define CPP_PP_NOT(BIT) CPP_PP_CAT_(CPP_PP_NOT_, BIT) -#define CPP_PP_NOT_0 1 -#define CPP_PP_NOT_1 0 - -#define CPP_PP_EMPTY() -#define CPP_PP_COMMA() , -#define CPP_PP_LBRACE() { -#define CPP_PP_RBRACE() } -#define CPP_PP_COMMA_IIF(X) \ - CPP_PP_IIF(X)(CPP_PP_EMPTY, CPP_PP_COMMA)() \ - /**/ - -#define CPP_assert(...) \ - static_assert(static_cast(__VA_ARGS__), \ - "Concept assertion failed : " #__VA_ARGS__) \ - /**/ -#define CPP_assert_msg static_assert - -#ifdef CPP_WORKAROUND_MSVC_784772 -#define CPP_EXPLICIT /**/ -#else -#define CPP_EXPLICIT explicit -#endif - -//////////////////////////////////////////////////////////////////////////////// -// CPP_def -// For defining concepts with a syntax similar to C++20. For example: -// -// CPP_def( -// // The assignable_from concept from the C++20 -// template(typename T, typename U) -// concept assignable_from, -// requires (T t, U &&u) ( -// t = (U &&) u, -// ::concepts::requires_> -// ) && -// std::is_lvalue_reference_v -// ); -#define CPP_def(DECL, ...) \ - CPP_PP_EVAL( \ - CPP_PP_DECL_DEF, \ - CPP_PP_CAT(CPP_PP_DEF_DECL_, DECL), \ - __VA_ARGS__) \ - /**/ -#define CPP_PP_DECL_DEF_NAME(...) \ - CPP_PP_CAT(CPP_PP_DEF_, __VA_ARGS__), \ - /**/ -#define CPP_PP_DECL_DEF(TPARAM, NAME, ...) \ - CPP_PP_CAT(CPP_PP_DECL_DEF_, CPP_PP_IS_PAREN(NAME))( \ - TPARAM, \ - NAME, \ - __VA_ARGS__) \ - /**/ -// The defn is of the form: -// template(typename A, typename B = void, typename... Rest) -// (concept Name)(A, B, Rest...), -// // requirements... -#define CPP_PP_DECL_DEF_1(TPARAM, NAME, ...) \ - CPP_PP_EVAL2( \ - CPP_PP_DECL_DEF_IMPL, \ - TPARAM, \ - CPP_PP_DECL_DEF_NAME NAME, \ - __VA_ARGS__) \ - /**/ -// The defn is of the form: -// template(typename A, typename B) -// concept Name, -// // requirements... -// Compute the template arguments (A, B) from the template introducer. -#define CPP_PP_DECL_DEF_0(TPARAM, NAME, ...) \ - CPP_PP_DECL_DEF_IMPL( \ - TPARAM, \ - CPP_PP_CAT(CPP_PP_DEF_, NAME), \ - (CPP_PP_CAT(CPP_PP_AUX_, TPARAM)), \ - __VA_ARGS__) \ - /**/ -// Expand the template definition into a struct and template alias like: -// struct NameConcept { -// template -// static auto Requires_(/* args (optional)*/) -> -// decltype(/*requirements...*/); -// template -// static constexpr auto is_satisfied_by(int) -> -// decltype(bool(&Requires_)) { return true; } -// template -// static constexpr bool is_satisfied_by(long) { return false; } -// }; -// template -// inline constexpr bool Name = NameConcept::is_satisfied_by(0); -#if CPP_CXX_CONCEPTS -// No requires expression -#define CPP_PP_DEF_IMPL_0(...) \ - __VA_ARGS__ \ - /**/ -// Requires expression -#define CPP_PP_DEF_IMPL_1(...) \ - CPP_PP_CAT(CPP_PP_DEF_IMPL_1_, __VA_ARGS__) \ - /**/ -#define CPP_PP_DEF_IMPL_1_requires \ - requires CPP_PP_DEF_IMPL_1_REQUIRES \ - /**/ -#define CPP_PP_DEF_IMPL_1_REQUIRES(...) \ - (__VA_ARGS__) CPP_PP_DEF_IMPL_1_REQUIRES_BODY \ - /**/ -#define CPP_PP_DEF_IMPL_1_REQUIRES_BODY(...) \ - { __VA_ARGS__; } \ - /**/ -#ifdef CPP_DOXYGEN_INVOKED -#define CPP_PP_DECL_DEF_IMPL(TPARAM, NAME, ARGS, ...) \ - CPP_PP_CAT(CPP_PP_DEF_, TPARAM) \ - META_CONCEPT NAME = CPP_PP_DEF_IMPL(__VA_ARGS__,)(__VA_ARGS__) \ - /**/ -#else -#define CPP_PP_DECL_DEF_IMPL(TPARAM, NAME, ARGS, ...) \ - inline namespace _eager_ { \ - CPP_PP_CAT(CPP_PP_DEF_, TPARAM) \ - META_CONCEPT NAME = CPP_PP_DEF_IMPL(__VA_ARGS__,)(__VA_ARGS__); \ - } \ - struct CPP_PP_CAT(NAME, _concept) { \ - CPP_PP_CAT(CPP_PP_DEF_, TPARAM) \ - struct Eval { \ - using Concept = CPP_PP_CAT(NAME, _concept); \ - CPP_EXPLICIT constexpr operator bool() const noexcept { \ - return (bool) _eager_::NAME; \ - } \ - constexpr auto operator!() const noexcept { \ - return ::concepts::detail::not_{}; \ - } \ - template \ - constexpr auto operator&&(That) const noexcept { \ - return ::concepts::detail::and_{}; \ - } \ - }; \ - }; \ - namespace lazy { \ - CPP_PP_CAT(CPP_PP_DEF_, TPARAM) \ - CPP_INLINE_VAR constexpr auto NAME = \ - CPP_PP_CAT(NAME, _concept)::Eval{}; \ - } \ - namespace defer { \ - using namespace _eager_; \ - } \ - using _concepts_int_ = int \ - /**/ -#endif -#else -// No requires expression: -#define CPP_PP_DEF_IMPL_0(...) \ - () -> ::concepts::detail::enable_if_t(__VA_ARGS__)> \ - /**/ -// Requires expression: -#define CPP_PP_DEF_IMPL_1(...) \ - CPP_PP_CAT(CPP_PP_DEF_IMPL_1_, __VA_ARGS__) )> \ - /**/ -#define CPP_PP_DEF_IMPL_1_requires(...) \ - (__VA_ARGS__) -> ::concepts::detail::enable_if_t(::concepts::detail::requires_ CPP_PP_DEF_REQUIRES_BODY \ - /**/ - #define CPP_PP_DEF_REQUIRES_BODY(...) \ - () \ - /**/ -#ifdef CPP_WORKAROUND_MSVC_780775 -#define CPP_PP_DECL_DEF_IMPL_HACK(ARGS) \ - template) = nullptr> \ - static constexpr bool impl(int) noexcept { return true; } \ - /**/ -#else -#define CPP_PP_DECL_DEF_IMPL_HACK(ARGS) \ - template \ - static constexpr decltype( \ - &C_::template Requires_, true) \ - impl(int) noexcept { return true; } \ - /**/ -#endif -#define CPP_PP_DECL_DEF_IMPL(TPARAM, NAME, ARGS, ...) \ - struct CPP_PP_CAT(NAME, _concept) { \ - using Concept = CPP_PP_CAT(NAME, _concept); \ - CPP_PP_IGNORE_CXX2A_COMPAT_BEGIN \ - CPP_PP_CAT(CPP_PP_DEF_, TPARAM) \ - static auto Requires_ CPP_PP_DEF_IMPL(__VA_ARGS__,)(__VA_ARGS__); \ - CPP_PP_IGNORE_CXX2A_COMPAT_END \ - CPP_PP_CAT(CPP_PP_DEF_, TPARAM) \ - struct Eval { \ - CPP_PP_DECL_DEF_IMPL_HACK(ARGS) \ - static constexpr bool impl(long) noexcept { return false; } \ - CPP_EXPLICIT constexpr operator bool() const noexcept { \ - return Eval::impl(0); \ - } \ - constexpr auto operator!() const noexcept { \ - return ::concepts::detail::not_{}; \ - } \ - template \ - constexpr auto operator&&(That) const noexcept { \ - return ::concepts::detail::and_{}; \ - } \ - }; \ - }; \ - CPP_PP_CAT(CPP_PP_DEF_, TPARAM) \ - CPP_INLINE_VAR constexpr bool NAME = \ - (bool)CPP_PP_CAT(NAME, _concept)::Eval{}; \ - namespace lazy { \ - CPP_PP_CAT(CPP_PP_DEF_, TPARAM) \ - CPP_INLINE_VAR constexpr auto NAME = \ - CPP_PP_CAT(NAME, _concept)::Eval{}; \ - } \ - namespace defer { \ - using namespace lazy; \ - } \ - using _concepts_int_ = int \ - /**/ -#endif - -#define CPP_PP_REQUIRES_PROBE_requires \ - CPP_PP_PROBE(~) \ - /**/ -#define CPP_PP_DEF_IMPL(REQUIRES, ...) \ - CPP_PP_CAT( \ - CPP_PP_DEF_IMPL_IS_PAREN_, \ - CPP_PP_IS_PAREN(REQUIRES))(REQUIRES) \ - /**/ -#define CPP_PP_DEF_IMPL_IS_PAREN_0(REQUIRES) \ - CPP_PP_CAT( \ - CPP_PP_DEF_IMPL_, \ - CPP_PP_CHECK(CPP_PP_CAT(CPP_PP_REQUIRES_PROBE_, REQUIRES))) \ - /**/ -#define CPP_PP_DEF_IMPL_IS_PAREN_1(REQUIRES) \ - CPP_PP_DEF_IMPL_0 \ - /**/ -#define CPP_PP_DEF_DECL_template(...) \ - template(__VA_ARGS__), \ - /**/ -#define CPP_PP_DEF_template(...) \ - template<__VA_ARGS__> \ - /**/ -#define CPP_PP_DEF_concept -#define CPP_PP_DEF_class -#define CPP_PP_DEF_typename -#define CPP_PP_DEF_int -#define CPP_PP_DEF_bool -#define CPP_PP_DEF_size_t -#define CPP_PP_DEF_unsigned -#define CPP_PP_AUX_template(...) \ - CPP_PP_CAT2( \ - CPP_PP_TPARAM_, \ - CPP_PP_COUNT(__VA_ARGS__))(__VA_ARGS__) \ - /**/ -#define CPP_PP_TPARAM_1(_1) \ - CPP_PP_CAT2(CPP_PP_DEF_, _1) -#define CPP_PP_TPARAM_2(_1, ...) \ - CPP_PP_CAT2(CPP_PP_DEF_, _1), CPP_PP_TPARAM_1(__VA_ARGS__) -#define CPP_PP_TPARAM_3(_1, ...) \ - CPP_PP_CAT2(CPP_PP_DEF_, _1), CPP_PP_TPARAM_2(__VA_ARGS__) -#define CPP_PP_TPARAM_4(_1, ...) \ - CPP_PP_CAT2(CPP_PP_DEF_, _1), CPP_PP_TPARAM_3(__VA_ARGS__) -#define CPP_PP_TPARAM_5(_1, ...) \ - CPP_PP_CAT2(CPP_PP_DEF_, _1), CPP_PP_TPARAM_4(__VA_ARGS__) - -#define CPP_PP_PROBE_EMPTY_PROBE_CPP_PP_PROBE_EMPTY \ - CPP_PP_PROBE(~) \ - -#define CPP_PP_PROBE_EMPTY() -#define CPP_PP_IS_NOT_EMPTY(...) \ - CPP_PP_CHECK(CPP_PP_CAT(CPP_PP_PROBE_EMPTY_PROBE_, \ - CPP_PP_PROBE_EMPTY __VA_ARGS__ ())) \ - /**/ - -//////////////////////////////////////////////////////////////////////////////// -// CPP_template -// Usage: -// CPP_template(typename A, typename B) -// (requires Concept1 && Concept2) -// void foo(A a, B b) -// {} -#if CPP_CXX_CONCEPTS -#define CPP_template(...) \ - template<__VA_ARGS__> CPP_PP_EXPAND \ - /**/ -#define CPP_template_def CPP_template \ - /**/ -#define CPP_member -#define CPP_ctor(TYPE) TYPE CPP_CTOR_IMPL_1_ -#define CPP_CTOR_IMPL_1_(...) \ - (__VA_ARGS__) CPP_PP_EXPAND \ - /**/ -#else -#define CPP_template \ - CPP_template_sfinae \ - /**/ -#define CPP_template_def CPP_template_def_sfinae \ - /**/ -#define CPP_member CPP_member_sfinae -#define CPP_ctor CPP_ctor_sfinae -#endif - -#define CPP_template_sfinae(...) \ - template<__VA_ARGS__ CPP_TEMPLATE_SFINAE_AUX_ \ - /**/ -#define CPP_TEMPLATE_SFINAE_AUX_(...) , \ - bool CPP_false_ = false, \ - ::concepts::detail::enable_if_t(\ - CPP_PP_CAT(CPP_TEMPLATE_SFINAE_AUX_3_, __VA_ARGS__)) || \ - CPP_false_> = 0> \ - /**/ -#define CPP_template_def_sfinae(...) \ - template<__VA_ARGS__ CPP_TEMPLATE_DEF_SFINAE_AUX_ \ - /**/ -#define CPP_TEMPLATE_DEF_SFINAE_AUX_(...) , \ - bool CPP_false_, \ - ::concepts::detail::enable_if_t( \ - CPP_PP_CAT(CPP_TEMPLATE_SFINAE_AUX_3_, __VA_ARGS__)) || \ - CPP_false_>> \ - /**/ -#define CPP_TEMPLATE_SFINAE_AUX_3_requires -#define CPP_member_sfinae \ - CPP_broken_friend_member \ - /**/ -#define CPP_ctor_sfinae(TYPE) TYPE CPP_CTOR_SFINAE_IMPL_1_ -#define CPP_CTOR_SFINAE_IMPL_1_(...) \ - (__VA_ARGS__ \ - CPP_PP_COMMA_IIF( \ - CPP_PP_NOT(CPP_PP_IS_NOT_EMPTY(__VA_ARGS__))) \ - CPP_CTOR_SFINAE_REQUIRES \ - /**/ -#define CPP_CTOR_SFINAE_PROBE_NOEXCEPT_noexcept \ - CPP_PP_PROBE(~) \ - /**/ -#define CPP_CTOR_SFINAE_MAKE_PROBE(FIRST,...) \ - CPP_PP_CAT(CPP_CTOR_SFINAE_PROBE_NOEXCEPT_, FIRST) \ - /**/ -#define CPP_CTOR_SFINAE_REQUIRES(...) \ - CPP_PP_CAT(CPP_CTOR_SFINAE_REQUIRES_, \ - CPP_PP_CHECK(CPP_CTOR_SFINAE_MAKE_PROBE(__VA_ARGS__,)))(__VA_ARGS__) \ - /**/ -// No noexcept-clause: -#define CPP_CTOR_SFINAE_REQUIRES_0(...) \ - ::concepts::detail::enable_if_t< \ - ::concepts::detail::Nil, \ - CPP_false(::concepts::detail::xNil{}) || \ - static_cast( \ - CPP_PP_CAT(CPP_TEMPLATE_SFINAE_AUX_3_, __VA_ARGS__))> = {}) \ - /**/ -// Yes noexcept-clause: -#define CPP_CTOR_SFINAE_REQUIRES_1(...) \ - ::concepts::detail::enable_if_t< \ - ::concepts::detail::Nil, \ - CPP_false(::concepts::detail::xNil{}) || \ - static_cast(CPP_PP_CAT(CPP_TEMPLATE_SFINAE_AUX_3_, CPP_PP_CAT( \ - CPP_CTOR_SFINAE_EAT_NOEXCEPT_, __VA_ARGS__)))> = {}) \ - CPP_PP_EXPAND( \ - CPP_PP_CAT(CPP_CTOR_SFINAE_SHOW_NOEXCEPT_, __VA_ARGS__))) \ - /**/ -#define CPP_CTOR_SFINAE_EAT_NOEXCEPT_noexcept(...) -#define CPP_CTOR_SFINAE_SHOW_NOEXCEPT_noexcept(...) \ - noexcept(__VA_ARGS__) CPP_PP_EAT CPP_PP_LPAREN \ - /**/ - -#ifdef CPP_DOXYGEN_INVOKED -#define CPP_broken_friend_ret(...) \ - __VA_ARGS__ CPP_PP_EXPAND \ - /**/ -#else -#define CPP_broken_friend_ret(...) \ - ::concepts::detail::enable_if_t<__VA_ARGS__, \ - CPP_BROKEN_FRIEND_RETURN_TYPE_AUX_ \ - /**/ -#define CPP_BROKEN_FRIEND_RETURN_TYPE_AUX_(...) \ - CPP_BROKEN_FRIEND_RETURN_TYPE_AUX_3_(CPP_PP_CAT( \ - CPP_TEMPLATE_AUX_2_, __VA_ARGS__)) \ - /**/ -#define CPP_TEMPLATE_AUX_2_requires -#define CPP_BROKEN_FRIEND_RETURN_TYPE_AUX_3_(...) \ - static_cast(__VA_ARGS__) || CPP_false(::concepts::detail::xNil{})> \ - /**/ -#ifdef CPP_WORKAROUND_MSVC_779763 -#define CPP_broken_friend_member \ - template<::concepts::detail::CPP_false_t const &CPP_false = \ - ::concepts::detail::CPP_false_> \ - /**/ -#else // ^^^ workaround / no workaround vvv -#define CPP_broken_friend_member \ - template \ - /**/ -#endif // CPP_WORKAROUND_MSVC_779763 -#endif - -#if CPP_CXX_CONCEPTS -#define CPP_ret(...) \ - __VA_ARGS__ CPP_PP_EXPAND \ - /**/ -#else -#define CPP_ret \ - CPP_broken_friend_ret \ - /**/ -#endif - -//////////////////////////////////////////////////////////////////////////////// -// CPP_fun -#if CPP_CXX_CONCEPTS -#define CPP_FUN_IMPL_1_(...) \ - (__VA_ARGS__) \ - CPP_PP_EXPAND \ - /**/ -#define CPP_fun(X) X CPP_FUN_IMPL_1_ -#else -#define CPP_FUN_IMPL_1_(...) \ - (__VA_ARGS__ \ - CPP_PP_COMMA_IIF( \ - CPP_PP_NOT(CPP_PP_IS_NOT_EMPTY(__VA_ARGS__))) \ - CPP_FUN_IMPL_REQUIRES \ - /**/ - -#define CPP_FUN_IMPL_REQUIRES(...) \ - CPP_FUN_IMPL_SELECT_CONST_(__VA_ARGS__,)(__VA_ARGS__) \ - /**/ - -#define CPP_FUN_IMPL_SELECT_CONST_(MAYBE_CONST, ...) \ - CPP_PP_CAT(CPP_FUN_IMPL_SELECT_CONST_, \ - CPP_PP_CHECK(CPP_PP_CAT( \ - CPP_PP_PROBE_CONST_PROBE_, MAYBE_CONST))) \ - /**/ - -#define CPP_PP_PROBE_CONST_PROBE_const CPP_PP_PROBE(~) - -#define CPP_FUN_IMPL_SELECT_CONST_1(...) \ - CPP_PP_EVAL( \ - CPP_FUN_IMPL_SELECT_CONST_NOEXCEPT_, \ - CPP_PP_CAT(CPP_FUN_IMPL_EAT_CONST_, __VA_ARGS__),)( \ - CPP_PP_CAT(CPP_FUN_IMPL_EAT_CONST_, __VA_ARGS__)) \ - /**/ - -#define CPP_FUN_IMPL_SELECT_CONST_NOEXCEPT_(MAYBE_NOEXCEPT, ...) \ - CPP_PP_CAT(CPP_FUN_IMPL_SELECT_CONST_NOEXCEPT_, \ - CPP_PP_CHECK(CPP_PP_CAT( \ - CPP_PP_PROBE_NOEXCEPT_PROBE_, MAYBE_NOEXCEPT))) \ - /**/ - -#define CPP_PP_PROBE_NOEXCEPT_PROBE_noexcept CPP_PP_PROBE(~) - -#define CPP_FUN_IMPL_SELECT_CONST_NOEXCEPT_0(...) \ - ::concepts::detail::enable_if_t< \ - ::concepts::detail::Nil, \ - static_cast( \ - CPP_PP_CAT(CPP_FUN_IMPL_EAT_REQUIRES_, __VA_ARGS__)) || \ - CPP_false(::concepts::detail::xNil{})> = {}) const \ - /**/ - -#define CPP_FUN_IMPL_SELECT_CONST_NOEXCEPT_1(...) \ - ::concepts::detail::enable_if_t< \ - ::concepts::detail::Nil, \ - static_cast(CPP_PP_CAT(CPP_FUN_IMPL_EAT_REQUIRES_, CPP_PP_CAT( \ - CPP_FUN_IMPL_EAT_NOEXCEPT_, __VA_ARGS__))) || \ - CPP_false(::concepts::detail::xNil{})> = {}) const CPP_PP_EXPAND( \ - CPP_PP_CAT(CPP_FUN_IMPL_SHOW_NOEXCEPT_, __VA_ARGS__))) \ - /**/ - -#define CPP_FUN_IMPL_EAT_NOEXCEPT_noexcept(...) -#define CPP_FUN_IMPL_SHOW_NOEXCEPT_noexcept(...) \ - noexcept(__VA_ARGS__) CPP_PP_EAT CPP_PP_LPAREN \ - /**/ - -#define CPP_FUN_IMPL_EAT_NOEXCEPT_noexcept(...) - -#define CPP_FUN_IMPL_EXPAND_NOEXCEPT_noexcept(...) \ - noexcept(__VA_ARGS__) \ - /**/ - -#define CPP_FUN_IMPL_SELECT_CONST_0(...) \ - CPP_FUN_IMPL_SELECT_NONCONST_NOEXCEPT_(__VA_ARGS__,)(__VA_ARGS__) \ - /**/ - -#define CPP_FUN_IMPL_SELECT_NONCONST_NOEXCEPT_(MAYBE_NOEXCEPT, ...) \ - CPP_PP_CAT(CPP_FUN_IMPL_SELECT_NONCONST_NOEXCEPT_, \ - CPP_PP_CHECK(CPP_PP_CAT( \ - CPP_PP_PROBE_NOEXCEPT_PROBE_, MAYBE_NOEXCEPT))) \ - /**/ - -#define CPP_FUN_IMPL_SELECT_NONCONST_NOEXCEPT_0(...) \ - ::concepts::detail::enable_if_t< \ - ::concepts::detail::Nil, \ - static_cast( \ - CPP_PP_CAT(CPP_FUN_IMPL_EAT_REQUIRES_, __VA_ARGS__)) || \ - CPP_false(::concepts::detail::xNil{})> = {}) \ - /**/ - -#define CPP_FUN_IMPL_SELECT_NONCONST_NOEXCEPT_1(...) \ - ::concepts::detail::enable_if_t< \ - ::concepts::detail::Nil, \ - static_cast(CPP_PP_CAT(CPP_FUN_IMPL_EAT_REQUIRES_, CPP_PP_CAT( \ - CPP_FUN_IMPL_EAT_NOEXCEPT_, __VA_ARGS__))) || \ - CPP_false(::concepts::detail::xNil{})> = {}) \ - CPP_PP_EXPAND(CPP_PP_CAT(CPP_FUN_IMPL_SHOW_NOEXCEPT_, __VA_ARGS__))) \ - /**/ - -#define CPP_FUN_IMPL_EAT_CONST_const -#define CPP_FUN_IMPL_EAT_REQUIRES_requires - -//////////////////////////////////////////////////////////////////////////////// -// CPP_fun -// Usage: -// template -// void CPP_fun(foo)(A a, B b)([const]opt [noexcept(true)]opt -// requires Concept1 && Concept2) -// {} -// -// Note: This macro cannot be used when the last function argument is a -// parameter pack. -#define CPP_fun(X) X CPP_FUN_IMPL_1_ -#endif - -//////////////////////////////////////////////////////////////////////////////// -// CPP_auto_fun -// Usage: -// template -// auto CPP_auto_fun(foo)(A a, B b)([const]opt [noexcept(true)]opt)opt -// ( -// return a + b -// ) -#define CPP_auto_fun(X) X CPP_AUTO_FUN_IMPL_ -#define CPP_AUTO_FUN_IMPL_(...) (__VA_ARGS__) CPP_AUTO_FUN_RETURNS_ -#define CPP_AUTO_FUN_RETURNS_(...) \ - CPP_AUTO_FUN_SELECT_RETURNS_(__VA_ARGS__,)(__VA_ARGS__) \ - /**/ -#define CPP_AUTO_FUN_SELECT_RETURNS_(MAYBE_CONST, ...) \ - CPP_PP_CAT(CPP_AUTO_FUN_RETURNS_CONST_, \ - CPP_PP_CHECK(CPP_PP_CAT( \ - CPP_PP_PROBE_CONST_MUTABLE_PROBE_, MAYBE_CONST))) \ - /**/ -#define CPP_PP_PROBE_CONST_MUTABLE_PROBE_const CPP_PP_PROBE_N(~, 1) -#define CPP_PP_PROBE_CONST_MUTABLE_PROBE_mutable CPP_PP_PROBE_N(~, 2) -#define CPP_PP_EAT_MUTABLE_mutable -#define CPP_AUTO_FUN_RETURNS_CONST_2(...) \ - CPP_PP_CAT(CPP_PP_EAT_MUTABLE_, __VA_ARGS__) CPP_AUTO_FUN_RETURNS_CONST_0 -#define CPP_AUTO_FUN_RETURNS_CONST_1(...) \ - __VA_ARGS__ CPP_AUTO_FUN_RETURNS_CONST_0 \ - /**/ -#define CPP_AUTO_FUN_RETURNS_CONST_0(...) \ - CPP_PP_EVAL(CPP_AUTO_FUN_DECLTYPE_NOEXCEPT_, \ - CPP_PP_CAT(CPP_AUTO_FUN_RETURNS_, __VA_ARGS__)) \ - /**/ -#define CPP_AUTO_FUN_RETURNS_return - -#ifdef __cpp_guaranteed_copy_elision -#define CPP_AUTO_FUN_DECLTYPE_NOEXCEPT_(...) \ - noexcept(noexcept(__VA_ARGS__)) -> decltype(__VA_ARGS__) \ - { return (__VA_ARGS__); } \ - /**/ -#else -#define CPP_AUTO_FUN_DECLTYPE_NOEXCEPT_(...) \ - noexcept(noexcept(decltype(__VA_ARGS__)(__VA_ARGS__))) -> \ - decltype(__VA_ARGS__) \ - { return (__VA_ARGS__); } \ - /**/ -#endif - -namespace concepts -{ - template - using bool_ = std::integral_constant; - -#if defined(__cpp_fold_expressions) && __cpp_fold_expressions >= 201603 - template - CPP_INLINE_VAR constexpr bool and_v = (Bs &&...); - - template - CPP_INLINE_VAR constexpr bool or_v = (Bs ||...); -#else - namespace detail - { - template - struct bools; - } // namespace detail - - template - CPP_INLINE_VAR constexpr bool and_v = - META_IS_SAME(detail::bools, detail::bools); - - template - CPP_INLINE_VAR constexpr bool or_v = - !META_IS_SAME(detail::bools, detail::bools); -#endif - - namespace detail - { - template - constexpr bool requires_() - { - return true; - } - template - struct and_; - template - struct not_ - { - CPP_EXPLICIT constexpr operator bool() const noexcept - { - return !(bool) T{}; - } - constexpr auto operator!() const noexcept - { - return T{}; - } - template - constexpr auto operator&&(That) const noexcept - { - return and_{}; - } - }; - template - struct and_ - { - static constexpr bool impl(std::false_type) noexcept - { - return false; - } - static constexpr bool impl(std::true_type) noexcept - { - return (bool) U{}; - } - CPP_EXPLICIT constexpr operator bool() const noexcept - { - return and_::impl(bool_<(bool) T{}>{}); - } - constexpr auto operator!() const noexcept - { - return not_{}; - } - template - constexpr auto operator&&(That) const noexcept - { - return detail::and_{}; - } - }; - - template - struct identity - { - template - using invoke = T; - }; - - template - using enable_if_t = meta::invoke>, T>; - - struct Nil - {}; - -#ifdef CPP_WORKAROUND_MSVC_779763 - enum class xNil {}; - - struct CPP_false_t - { - constexpr bool operator()(Nil) const noexcept - { - return false; - } - constexpr bool operator()(xNil) const noexcept - { - return false; - } - }; - - CPP_INLINE_VAR constexpr CPP_false_t CPP_false_{}; - - constexpr bool CPP_false(xNil) - { - return false; - } -#else - using xNil = Nil; -#endif - - constexpr bool CPP_false(Nil) - { - return false; - } - - template - using remove_cvref_t = - typename std::remove_cv::type>::type; - - CPP_def - ( - template(typename T, typename U) - concept weakly_equality_comparable_with_, - requires (detail::as_cref_t t, detail::as_cref_t u) - ( - (t == u) ? 1 : 0, - (t != u) ? 1 : 0, - (u == t) ? 1 : 0, - (u != t) ? 1 : 0 - ) - ); - } // namespace detail - -#if defined(__clang__) || defined(_MSC_VER) - template - ::concepts::detail::enable_if_t requires_() - {} -#else - template - CPP_INLINE_VAR constexpr ::concepts::detail::enable_if_t requires_ = 0; -#endif - - inline namespace defs - { - //////////////////////////////////////////////////////////////////////////////////////// - // Utility concepts - //////////////////////////////////////////////////////////////////////////////////////// - - CPP_def - ( - template(bool B) - (concept is_true)(B), - B - ); - - CPP_def - ( - template(typename... Args) - (concept type)(Args...), - true - ); - - CPP_def - ( - template(class T, template class Trait, typename... Args) - (concept satisfies)(T, Trait, Args...), - static_cast(Trait::type::value) - ); - - //////////////////////////////////////////////////////////////////////////////////////// - // Core language concepts - //////////////////////////////////////////////////////////////////////////////////////// - - CPP_def - ( - template(typename A, typename B) - concept same_as, - META_IS_SAME(A, B) && META_IS_SAME(B, A) - ); - - /// \cond - CPP_def - ( - template(typename A, typename B) - concept not_same_as_, - (!same_as, detail::remove_cvref_t>) - ); - - // Workaround bug in the Standard Library: - // From cannot be an incomplete class type despite that - // is_convertible should be equivalent to is_convertible - // in such a case. - CPP_def - ( - template(typename From, typename To) - concept implicitly_convertible_to, - std::is_convertible::type, To>::value - ); - - CPP_def - ( - template(typename From, typename To) - concept explicitly_convertible_to, - requires (From (&from)()) - ( - static_cast(from()) - ) - ); - /// \endcond - - CPP_def - ( - template(typename From, typename To) - concept convertible_to, - implicitly_convertible_to && - explicitly_convertible_to - ); - - CPP_def - ( - template(typename T, typename U) - concept derived_from, - META_IS_BASE_OF(U, T) && - convertible_to - ); - - CPP_def - ( - template(typename T, typename U) - concept common_reference_with, - same_as, common_reference_t> && - convertible_to> && - convertible_to> - ); - - CPP_def - ( - template(typename T, typename U) - concept common_with, - same_as, common_type_t> && - convertible_to> && - convertible_to> && - common_reference_with< - typename std::add_lvalue_reference::type, - typename std::add_lvalue_reference::type> && - common_reference_with< - typename std::add_lvalue_reference>::type, - common_reference_t< - typename std::add_lvalue_reference::type, - typename std::add_lvalue_reference::type>> - ); - - CPP_def - ( - template(typename T) - concept integral, - std::is_integral::value - ); - - CPP_def - ( - template(typename T) - concept signed_integral, - integral && - std::is_signed::value - ); - - CPP_def - ( - template(typename T) - concept unsigned_integral, - integral && - !signed_integral - ); - - CPP_def - ( - template(typename T, typename U) - concept assignable_from, - requires (T t, U &&u) - ( - t = (U &&) u, - requires_> - ) && - std::is_lvalue_reference::value - ); - - CPP_def - ( - template(typename T) - concept swappable, - requires (T &t, T &u) - ( - concepts::swap(t, u) - ) - ); - - CPP_def - ( - template(typename T, typename U) - concept swappable_with, - requires (T &&t, U &&u) - ( - concepts::swap((T &&) t, (T &&) t), - concepts::swap((U &&) u, (U &&) u), - concepts::swap((U &&) u, (T &&) t), - concepts::swap((T &&) t, (U &&) u) - ) && - common_reference_with, detail::as_cref_t> - ); - - //////////////////////////////////////////////////////////////////////////////////////////// - // Comparison concepts - //////////////////////////////////////////////////////////////////////////////////////////// - - CPP_def - ( - template(typename T) - concept equality_comparable, - detail::weakly_equality_comparable_with_ - ); - - CPP_def - ( - template(typename T, typename U) - concept equality_comparable_with, - equality_comparable && - equality_comparable && - detail::weakly_equality_comparable_with_ && - common_reference_with, detail::as_cref_t> && - equality_comparable< - common_reference_t, detail::as_cref_t>> - ); - - CPP_def - ( - template(typename T) - concept totally_ordered, - requires (detail::as_cref_t t, detail::as_cref_t u) - ( - t < u ? 1 : 0, - t > u ? 1 : 0, - u <= t ? 1 : 0, - u >= t ? 1 : 0 - ) && - equality_comparable - ); - - CPP_def - ( - template(typename T, typename U) - concept totally_ordered_with, - requires (detail::as_cref_t t, detail::as_cref_t u) - ( - t < u ? 1 : 0, - t > u ? 1 : 0, - t <= u ? 1 : 0, - t >= u ? 1 : 0, - u < t ? 1 : 0, - u > t ? 1 : 0, - u <= t ? 1 : 0, - u >= t ? 1 : 0 - ) && - totally_ordered && - totally_ordered && - equality_comparable_with && - common_reference_with, detail::as_cref_t> && - totally_ordered< - common_reference_t, detail::as_cref_t>> - ); - - //////////////////////////////////////////////////////////////////////////////////////////// - // Object concepts - //////////////////////////////////////////////////////////////////////////////////////////// - - CPP_def - ( - template(typename T) - concept destructible, - std::is_nothrow_destructible::value - ); - - CPP_def - ( - template(typename T, typename... Args) - (concept constructible_from)(T, Args...), - destructible && - META_IS_CONSTRUCTIBLE(T, Args...) - ); - - CPP_def - ( - template(typename T) - concept default_constructible, - constructible_from - ); - - CPP_def - ( - template(typename T) - concept move_constructible, - constructible_from && - convertible_to - ); - - CPP_def - ( - template(typename T) - concept copy_constructible, - move_constructible && - constructible_from && - constructible_from && - constructible_from && - convertible_to && - convertible_to && - convertible_to - ); - - CPP_def - ( - template(typename T) - concept movable, - std::is_object::value && - move_constructible && - assignable_from && - swappable - ); - - CPP_def - ( - template(typename T) - concept copyable, - copy_constructible && - movable && - assignable_from - ); - - CPP_def - ( - template(typename T) - concept semiregular, - copyable && - default_constructible - // Axiom: copies are independent. See Fundamentals of Generic Programming - // http://www.stepanovpapers.com/DeSt98.pdf - ); - - CPP_def - ( - template(typename T) - concept regular, - semiregular && - equality_comparable - ); - } // inline namespace defs - - template - struct is_satisfied_by - : meta::bool_(typename Concept::template Eval{})> - {}; - - // For automatically generating tags corresponding to concept - // subsumption relationships, for use with tag dispatching. - template - struct tag - : Base - {}; - - template - using tag_of = - meta::reverse_fold< - meta::find_if< - Concepts, - meta::bind_back, Ts...>>, - meta::nil_, - meta::flip>>; -} // namespace concepts - -CPP_PP_IGNORE_CXX2A_COMPAT_END - -#endif // RANGES_V3_UTILITY_CONCEPTS_HPP diff --git a/vendor/range-v3/range-v3/include/concepts/swap.hpp b/vendor/range-v3/range-v3/include/concepts/swap.hpp deleted file mode 100644 index ae99faa2..00000000 --- a/vendor/range-v3/range-v3/include/concepts/swap.hpp +++ /dev/null @@ -1,326 +0,0 @@ -/// \file -// Concepts library -// -// Copyright Eric Niebler 2013-present -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 - -#ifndef CPP_SWAP_HPP -#define CPP_SWAP_HPP - -#include -#include -#include -#include - -// Note: constexpr implies inline, to retain the same visibility -// C++14 constexpr functions are inline in C++11 -#if (defined(__cpp_constexpr) && __cpp_constexpr >= 201304L) ||\ - (!defined(__cpp_constexpr) && __cplusplus >= 201402L) -#define CPP_CXX14_CONSTEXPR constexpr -#else -#define CPP_CXX14_CONSTEXPR inline -#endif - -#ifndef CPP_CXX_INLINE_VARIABLES -#ifdef __cpp_inline_variables // TODO: fix this if SD-6 picks another name -#define CPP_CXX_INLINE_VARIABLES __cpp_inline_variables -// TODO: remove once clang defines __cpp_inline_variables (or equivalent) -#elif defined(__clang__) && \ - (__clang_major__ > 3 || __clang_major__ == 3 && __clang_minor__ == 9) && \ - __cplusplus > 201402L -#define CPP_CXX_INLINE_VARIABLES 201606L -#else -#define CPP_CXX_INLINE_VARIABLES __cplusplus -#endif // __cpp_inline_variables -#endif // CPP_CXX_INLINE_VARIABLES - -#if defined(_MSC_VER) && !defined(__clang__) -#define CPP_WORKAROUND_MSVC_895622 // Error when phase 1 name binding finds only deleted function -#endif - -#if CPP_CXX_INLINE_VARIABLES < 201606L -#define CPP_INLINE_VAR -#define CPP_INLINE_VARIABLE(type, name) \ - inline namespace \ - { \ - constexpr auto &name = ::concepts::detail::static_const::value; \ - } \ - /**/ -#else // CPP_CXX_INLINE_VARIABLES >= 201606L -#define CPP_INLINE_VAR inline -#define CPP_INLINE_VARIABLE(type, name) \ - inline constexpr type name{}; \ - /**/ -#endif // CPP_CXX_INLINE_VARIABLES - -#if CPP_CXX_INLINE_VARIABLES < 201606L -#define CPP_DEFINE_CPO(type, name) \ - inline namespace \ - { \ - constexpr auto &name = ::concepts::detail::static_const::value; \ - } \ - /**/ -#else // CPP_CXX_INLINE_VARIABLES >= 201606L -#define CPP_DEFINE_CPO(type, name) \ - inline namespace _ \ - { \ - inline constexpr type name{}; \ - } \ - /**/ -#endif // CPP_CXX_INLINE_VARIABLES - -#if defined(_MSC_VER) && !defined(__clang__) -#define CPP_DIAGNOSTIC_IGNORE_INIT_LIST_LIFETIME -#else // ^^^ defined(_MSC_VER) ^^^ / vvv !defined(_MSC_VER) vvv -#if defined(__GNUC__) || defined(__clang__) -#define CPP_PRAGMA(X) _Pragma(#X) -#define CPP_DIAGNOSTIC_IGNORE_PRAGMAS \ - CPP_PRAGMA(GCC diagnostic ignored "-Wpragmas") -#define CPP_DIAGNOSTIC_IGNORE(X) \ - CPP_DIAGNOSTIC_IGNORE_PRAGMAS \ - CPP_PRAGMA(GCC diagnostic ignored "-Wunknown-pragmas") \ - CPP_PRAGMA(GCC diagnostic ignored X) -#define CPP_DIAGNOSTIC_IGNORE_INIT_LIST_LIFETIME \ - CPP_DIAGNOSTIC_IGNORE("-Wunknown-warning-option") \ - CPP_DIAGNOSTIC_IGNORE("-Winit-list-lifetime") -#else -#define CPP_DIAGNOSTIC_IGNORE_INIT_LIST_LIFETIME -#endif -#endif // MSVC/Generic configuration switch - -namespace concepts -{ - /// \cond - namespace detail - { - template - CPP_INLINE_VAR constexpr bool is_movable_v = - std::is_object::value && - std::is_move_constructible::value && - std::is_move_assignable::value; - - template - struct static_const - { - static constexpr T const value {}; - }; - template - constexpr T const static_const::value; - } - /// \endcond - - template - struct is_swappable; - - template - struct is_nothrow_swappable; - - template - struct is_swappable_with; - - template - struct is_nothrow_swappable_with; - - template - CPP_CXX14_CONSTEXPR - meta::if_c< - std::is_move_constructible::value && - std::is_assignable::value, T> - exchange(T &t, U &&u) - noexcept( - std::is_nothrow_move_constructible::value && - std::is_nothrow_assignable::value) - { - T tmp((T &&) t); - t = (U &&) u; - CPP_DIAGNOSTIC_IGNORE_INIT_LIST_LIFETIME - return tmp; - } - - /// \cond - namespace adl_swap_detail - { - struct nope - {}; - - // Intentionally create an ambiguity with std::swap, which is - // (possibly) unconstrained. - template - nope swap(T &, T &) = delete; - - template - nope swap(T (&)[N], T (&)[N]) = delete; - -#ifdef CPP_WORKAROUND_MSVC_895622 - nope swap(); -#endif - - template - decltype(swap(std::declval(), std::declval())) try_adl_swap_(int); - - template - nope try_adl_swap_(long); - - template - CPP_INLINE_VAR constexpr bool is_adl_swappable_v = - !META_IS_SAME(decltype(adl_swap_detail::try_adl_swap_(42)), nope); - - struct swap_fn - { - // Dispatch to user-defined swap found via ADL: - template - CPP_CXX14_CONSTEXPR - meta::if_c> - operator()(T &&t, U &&u) const - noexcept(noexcept(swap((T &&) t, (U &&) u))) - { - swap((T &&) t, (U &&) u); - } - - // For intrinsically swappable (i.e., movable) types for which - // a swap overload cannot be found via ADL, swap by moving. - template - CPP_CXX14_CONSTEXPR - meta::if_c< - !is_adl_swappable_v && - detail::is_movable_v> - operator()(T &a, T &b) const - noexcept(noexcept(b = concepts::exchange(a, (T &&) b))) - { - b = concepts::exchange(a, (T &&) b); - } - - // For arrays of intrinsically swappable (i.e., movable) types - // for which a swap overload cannot be found via ADL, swap array - // elements by moving. - template - CPP_CXX14_CONSTEXPR - meta::if_c< - !is_adl_swappable_v && - is_swappable_with::value> - operator()(T (&t)[N], U (&u)[N]) const - noexcept(is_nothrow_swappable_with::value) - { - for(std::size_t i = 0; i < N; ++i) - (*this)(t[i], u[i]); - } - - // For rvalue pairs and tuples of swappable types, swap the - // members. This permits code like: - // ranges::swap(std::tie(a,b,c), std::tie(d,e,f)); - template - CPP_CXX14_CONSTEXPR - meta::if_c::value && is_swappable_with::value> - operator()(std::pair &&left, std::pair &&right) const - noexcept( - is_nothrow_swappable_with::value && - is_nothrow_swappable_with::value) - { - swap_fn()(static_cast &&>(left).first, - static_cast &&>(right).first); - swap_fn()(static_cast &&>(left).second, - static_cast &&>(right).second); - } - - template - CPP_CXX14_CONSTEXPR - meta::if_c::value...>::value> - operator()(std::tuple &&left, std::tuple &&right) const - noexcept(meta::and_c::value...>::value) - { - swap_fn::impl( - static_cast &&>(left), - static_cast &&>(right), - meta::make_index_sequence{}); - } - - private: - template - static constexpr int ignore_unused(Ts &&...) - { - return 0; - } - template - CPP_CXX14_CONSTEXPR - static void impl(T &&left, U &&right, meta::index_sequence) - { - (void) swap_fn::ignore_unused( - (swap_fn()(std::get(static_cast(left)), - std::get(static_cast(right))), 42)...); - } - }; - - template - struct is_swappable_with_ - : std::false_type - {}; - - template - struct is_swappable_with_(), std::declval())), - decltype(swap_fn()(std::declval(), std::declval()))>> - : std::true_type - {}; - - template - struct is_nothrow_swappable_with_ - : meta::bool_(), std::declval())) && - noexcept(swap_fn()(std::declval(), std::declval()))> - {}; - - // Q: Should std::reference_wrapper be considered a proxy wrt swapping rvalues? - // A: No. Its operator= is currently defined to reseat the references, so - // std::swap(ra, rb) already means something when ra and rb are (lvalue) - // reference_wrappers. That reseats the reference wrappers but leaves the - // referents unmodified. Treating rvalue reference_wrappers differently would - // be confusing. - - // Q: Then why is it OK to "re"-define swap for pairs and tuples of references? - // A: Because as defined above, swapping an rvalue tuple of references has the same - // semantics as swapping an lvalue tuple of references. Rather than reseat the - // references, assignment happens *through* the references. - - // Q: But I have an iterator whose operator* returns an rvalue - // std::reference_wrapper. How do I make it model indirectly_swappable? - // A: With an overload of iter_swap. - } - /// \endcond - - /// \ingroup group-utility - template - struct is_swappable_with - : adl_swap_detail::is_swappable_with_ - {}; - - /// \ingroup group-utility - template - struct is_nothrow_swappable_with - : meta::and_< - is_swappable_with, - adl_swap_detail::is_nothrow_swappable_with_> - {}; - - /// \ingroup group-utility - template - struct is_swappable - : is_swappable_with - {}; - - /// \ingroup group-utility - template - struct is_nothrow_swappable - : is_nothrow_swappable_with - {}; - - /// \ingroup group-utility - /// \relates adl_swap_detail::swap_fn - CPP_DEFINE_CPO(adl_swap_detail::swap_fn, swap) -} - -#endif diff --git a/vendor/range-v3/range-v3/include/concepts/type_traits.hpp b/vendor/range-v3/range-v3/include/concepts/type_traits.hpp deleted file mode 100644 index f5db7875..00000000 --- a/vendor/range-v3/range-v3/include/concepts/type_traits.hpp +++ /dev/null @@ -1,363 +0,0 @@ -/// \file -// Concepts library -// -// Copyright Eric Niebler 2013-present -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/range-v3 - -#ifndef CPP_TYPE_TRAITS_HPP -#define CPP_TYPE_TRAITS_HPP - -#include -#include -#include -#include - -namespace concepts -{ - template - using remove_cvref_t = - typename std::remove_cv< - typename std::remove_reference::type>::type; - - /// \cond - namespace detail - { - template - using is_convertible = std::is_convertible>, To>; - - template - struct if_else_ - { - template - using invoke = U; - }; - template<> - struct if_else_ - { - template - using invoke = T; - }; - template - using if_else_t = meta::invoke, T, U>; - - template - struct if_ - {}; - template<> - struct if_ - { - template - using invoke = T; - }; - template - using if_t = meta::invoke, T>; - - template - struct _copy_cv_ - { - using type = To; - }; - template - struct _copy_cv_ - { - using type = To const; - }; - template - struct _copy_cv_ - { - using type = To volatile; - }; - template - struct _copy_cv_ - { - using type = To const volatile; - }; - template - using _copy_cv = meta::_t<_copy_cv_>; - - //////////////////////////////////////////////////////////////////////////////////////// - template - struct _builtin_common; - - template - using _builtin_common_t = meta::_t<_builtin_common>; - - template - using _cond_res = decltype(true ? std::declval() : std::declval()); - - template> - using _rref_res = - if_else_t::value, meta::_t> &&, R>; - - template - using _lref_res = _cond_res<_copy_cv &, _copy_cv &>; - - template - struct as_cref_ - { - using type = T const &; - }; - template - struct as_cref_ - { - using type = T const &; - }; - template - struct as_cref_ - { - using type = T const &; - }; - template<> - struct as_cref_ - { - using type = void; - }; - template<> - struct as_cref_ - { - using type = void const; - }; - - template - using as_cref_t = typename as_cref_::type; - - template - using decay_t = typename std::decay::type; - - #if !defined(__GNUC__) || defined(__clang__) - template - struct _builtin_common_2 - {}; - template - struct _builtin_common_2, as_cref_t>>> - : std::decay<_cond_res, as_cref_t>> - {}; - template - struct _builtin_common - : _builtin_common_2 - {}; - template - struct _builtin_common>::value && - is_convertible>::value>> - { - using type = _rref_res; - }; - template - struct _builtin_common - : meta::defer<_lref_res, T, U> - {}; - template - struct _builtin_common>::value>> - : _builtin_common - {}; - template - struct _builtin_common - : _builtin_common - {}; - #else - template - struct _builtin_common_ - {}; - template - struct _builtin_common_, as_cref_t>>> - : std::decay<_cond_res, as_cref_t>> - {}; - template - struct _builtin_common - : _builtin_common_ - {}; - template - struct _builtin_common_rr - : _builtin_common_ - {}; - template - struct _builtin_common_rr>::value && - is_convertible>::value>> - { - using type = _rref_res; - }; - template - struct _builtin_common - : _builtin_common_rr - {}; - template - struct _builtin_common - : meta::defer<_lref_res, T, U> - {}; - template - struct _builtin_common_lr - : _builtin_common_ - {}; - template - struct _builtin_common_lr>::value>> - : _builtin_common - {}; - template - struct _builtin_common - : _builtin_common_lr - {}; - template - struct _builtin_common - : _builtin_common - {}; - #endif - } - /// \endcond - - /// \addtogroup group-utility Utility - /// @{ - /// - - /// Users should specialize this to hook the \c common_with concept - /// until \c std gets a SFINAE-friendly \c std::common_type and there's - /// some sane way to deal with cv and ref qualifiers. - template - struct common_type - {}; - - template - struct common_type - : std::decay - {}; - - template - struct common_type - : detail::if_else_t< - (META_IS_SAME(detail::decay_t, T) && - META_IS_SAME(detail::decay_t, U) ), - meta::defer, - common_type, detail::decay_t>> - {}; - - template - using common_type_t = typename common_type::type; - - template - struct common_type - : meta::lazy::fold, T, meta::quote> - {}; - - /// @} - - /// \addtogroup group-utility Utility - /// @{ - /// - - /// Users can specialize this to hook the \c common_reference_with concept. - /// \sa `common_reference` - template< - typename T, - typename U, - template class TQual, - template class UQual> - struct basic_common_reference - {}; - - /// \cond - namespace detail - { - using _rref = - meta::quote_trait; - using _lref = - meta::quote_trait; - - template - struct _xref - { - template - using invoke = T; - }; - template - struct _xref - { - template - using invoke = - meta::_t, U>>>; - }; - template - struct _xref - { - template - using invoke = - meta::_t, U>>>; - }; - template - struct _xref - { - template - using invoke = U const; - }; - template - struct _xref - { - template - using invoke = U volatile; - }; - template - struct _xref - { - template - using invoke = U const volatile; - }; - - template - using _basic_common_reference = - basic_common_reference< - remove_cvref_t, - remove_cvref_t, - _xref::template invoke, - _xref::template invoke>; - - template - struct _common_reference2 - : if_else_t< - meta::is_trait<_basic_common_reference>::value, - _basic_common_reference, - common_type> - {}; - - template - struct _common_reference2>::value>> - : _builtin_common - {}; - } - /// \endcond - - /// Users can specialize this to hook the \c common_reference_with concept. - /// \sa `basic_common_reference` - template - struct common_reference - {}; - - template - struct common_reference - { - using type = T; - }; - - template - struct common_reference - : detail::_common_reference2 - {}; - - template - using common_reference_t = typename common_reference::type; - - template - struct common_reference - : meta::lazy::fold, T, meta::quote> - {}; - /// @} -} // namespace concepts - -#endif diff --git a/vendor/range-v3/range-v3/include/meta/meta.hpp b/vendor/range-v3/range-v3/include/meta/meta.hpp deleted file mode 100644 index 91eedc6a..00000000 --- a/vendor/range-v3/range-v3/include/meta/meta.hpp +++ /dev/null @@ -1,3940 +0,0 @@ -/// \file meta.hpp Tiny meta-programming library. -// -// Meta library -// -// Copyright Eric Niebler 2014-present -// -// Use, modification and distribution is subject to the -// Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// Project home: https://github.com/ericniebler/meta -// - -#ifndef META_HPP -#define META_HPP - -#include -#include -#include -#include -#include - -#ifdef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic ignored "-Wdocumentation-deprecated-sync" -#pragma GCC diagnostic ignored "-Wmissing-variable-declarations" -#endif - -/// \defgroup meta Meta -/// -/// A tiny metaprogramming library - -/// \defgroup trait Trait -/// Trait invocation/composition. -/// \ingroup meta - -/// \defgroup invocation Invocation -/// Trait invocation -/// \ingroup trait - -/// \defgroup composition Composition -/// Trait composition -/// \ingroup trait - -/// \defgroup logical Logical -/// Logical operations -/// \ingroup meta - -/// \defgroup algorithm Algorithms -/// Algorithms. -/// \ingroup meta - -/// \defgroup query Query/Search -/// Query and search algorithms -/// \ingroup algorithm - -/// \defgroup transformation Transformation -/// Transformation algorithms -/// \ingroup algorithm - -/// \defgroup runtime Runtime -/// Runtime algorithms -/// \ingroup algorithm - -/// \defgroup datatype Datatype -/// Datatypes. -/// \ingroup meta - -/// \defgroup list list_like -/// \ingroup datatype - -/// \defgroup integral Integer sequence -/// Equivalent to C++14's `std::integer_sequence` -/// \ingroup datatype - -/// \defgroup extension Extension -/// Extend meta with your own datatypes. -/// \ingroup datatype - -/// \defgroup math Math -/// Integral constant arithmetic. -/// \ingroup meta - -/// \defgroup lazy_trait lazy -/// \ingroup trait - -/// \defgroup lazy_invocation lazy -/// \ingroup invocation - -/// \defgroup lazy_composition lazy -/// \ingroup composition - -/// \defgroup lazy_logical lazy -/// \ingroup logical - -/// \defgroup lazy_query lazy -/// \ingroup query - -/// \defgroup lazy_transformation lazy -/// \ingroup transformation - -/// \defgroup lazy_list lazy -/// \ingroup list - -/// \defgroup lazy_datatype lazy -/// \ingroup datatype - -/// \defgroup lazy_math lazy -/// \ingroup math - -/// Tiny metaprogramming library -namespace meta -{ - namespace detail - { - /// Returns a \p T nullptr - template - constexpr T *_nullptr_v() - { - return nullptr; - } - -#if META_CXX_VARIABLE_TEMPLATES - template - META_INLINE_VAR constexpr T *nullptr_v = nullptr; -#endif - } // namespace detail - - /// An empty type. - /// \ingroup datatype - struct nil_ - { - }; - - /// Type alias for \p T::type. - /// \ingroup invocation - template - using _t = typename T::type; - -#if META_CXX_VARIABLE_TEMPLATES || defined(META_DOXYGEN_INVOKED) - /// Variable alias for \c T::type::value - /// \note Requires C++14 or greater. - /// \ingroup invocation - template - constexpr typename T::type::value_type _v = T::type::value; -#endif - - /// Lazy versions of meta actions - namespace lazy - { - /// \sa `meta::_t` - /// \ingroup lazy_invocation - template - using _t = defer<_t, T>; - } // namespace lazy - - /// An integral constant wrapper for \c std::size_t. - /// \ingroup integral - template - using size_t = std::integral_constant; - - /// An integral constant wrapper for \c bool. - /// \ingroup integral - template - using bool_ = std::integral_constant; - - /// An integral constant wrapper for \c int. - /// \ingroup integral - template - using int_ = std::integral_constant; - - /// An integral constant wrapper for \c char. - /// \ingroup integral - template - using char_ = std::integral_constant; - - /////////////////////////////////////////////////////////////////////////////////////////// - // Math operations - /// An integral constant wrapper around the result of incrementing the wrapped integer \c - /// T::type::value. - template - using inc = std::integral_constant; - - /// An integral constant wrapper around the result of decrementing the wrapped integer \c - /// T::type::value. - template - using dec = std::integral_constant; - - /// An integral constant wrapper around the result of adding the two wrapped integers - /// \c T::type::value and \c U::type::value. - /// \ingroup math - template - using plus = std::integral_constant; - - /// An integral constant wrapper around the result of subtracting the two wrapped integers - /// \c T::type::value and \c U::type::value. - /// \ingroup math - template - using minus = std::integral_constant; - - /// An integral constant wrapper around the result of multiplying the two wrapped integers - /// \c T::type::value and \c U::type::value. - /// \ingroup math - template - using multiplies = std::integral_constant; - - /// An integral constant wrapper around the result of dividing the two wrapped integers \c - /// T::type::value and \c U::type::value. - /// \ingroup math - template - using divides = std::integral_constant; - - /// An integral constant wrapper around the result of negating the wrapped integer - /// \c T::type::value. - /// \ingroup math - template - using negate = std::integral_constant; - - /// An integral constant wrapper around the remainder of dividing the two wrapped integers - /// \c T::type::value and \c U::type::value. - /// \ingroup math - template - using modulus = std::integral_constant; - - /// A Boolean integral constant wrapper around the result of comparing \c T::type::value and - /// \c U::type::value for equality. - /// \ingroup math - template - using equal_to = bool_; - - /// A Boolean integral constant wrapper around the result of comparing \c T::type::value and - /// \c U::type::value for inequality. - /// \ingroup math - template - using not_equal_to = bool_; - - /// A Boolean integral constant wrapper around \c true if \c T::type::value is greater than - /// \c U::type::value; \c false, otherwise. - /// \ingroup math - template - using greater = bool_<(T::type::value > U::type::value)>; - - /// A Boolean integral constant wrapper around \c true if \c T::type::value is less than \c - /// U::type::value; \c false, otherwise. - /// \ingroup math - template - using less = bool_<(T::type::value < U::type::value)>; - - /// A Boolean integral constant wrapper around \c true if \c T::type::value is greater than - /// or equal to \c U::type::value; \c false, otherwise. - /// \ingroup math - template - using greater_equal = bool_<(T::type::value >= U::type::value)>; - - /// A Boolean integral constant wrapper around \c true if \c T::type::value is less than or - /// equal to \c U::type::value; \c false, otherwise. - /// \ingroup math - template - using less_equal = bool_<(T::type::value <= U::type::value)>; - - /// An integral constant wrapper around the result of bitwise-and'ing the two wrapped - /// integers \c T::type::value and \c U::type::value. - /// \ingroup math - template - using bit_and = std::integral_constant; - - /// An integral constant wrapper around the result of bitwise-or'ing the two wrapped - /// integers \c T::type::value and \c U::type::value. - /// \ingroup math - template - using bit_or = std::integral_constant; - - /// An integral constant wrapper around the result of bitwise-exclusive-or'ing the two - /// wrapped integers \c T::type::value and \c U::type::value. - /// \ingroup math - template - using bit_xor = std::integral_constant; - - /// An integral constant wrapper around the result of bitwise-complementing the wrapped - /// integer \c T::type::value. - /// \ingroup math - template - using bit_not = std::integral_constant; - - namespace lazy - { - /// \sa 'meta::int' - /// \ingroup lazy_math - template - using inc = defer; - - /// \sa 'meta::dec' - /// \ingroup lazy_math - template - using dec = defer; - - /// \sa 'meta::plus' - /// \ingroup lazy_math - template - using plus = defer; - - /// \sa 'meta::minus' - /// \ingroup lazy_math - template - using minus = defer; - - /// \sa 'meta::multiplies' - /// \ingroup lazy_math - template - using multiplies = defer; - - /// \sa 'meta::divides' - /// \ingroup lazy_math - template - using divides = defer; - - /// \sa 'meta::negate' - /// \ingroup lazy_math - template - using negate = defer; - - /// \sa 'meta::modulus' - /// \ingroup lazy_math - template - using modulus = defer; - - /// \sa 'meta::equal_to' - /// \ingroup lazy_math - template - using equal_to = defer; - - /// \sa 'meta::not_equal_t' - /// \ingroup lazy_math - template - using not_equal_to = defer; - - /// \sa 'meta::greater' - /// \ingroup lazy_math - template - using greater = defer; - - /// \sa 'meta::less' - /// \ingroup lazy_math - template - using less = defer; - - /// \sa 'meta::greater_equal' - /// \ingroup lazy_math - template - using greater_equal = defer; - - /// \sa 'meta::less_equal' - /// \ingroup lazy_math - template - using less_equal = defer; - - /// \sa 'meta::bit_and' - /// \ingroup lazy_math - template - using bit_and = defer; - - /// \sa 'meta::bit_or' - /// \ingroup lazy_math - template - using bit_or = defer; - - /// \sa 'meta::bit_xor' - /// \ingroup lazy_math - template - using bit_xor = defer; - - /// \sa 'meta::bit_not' - /// \ingroup lazy_math - template - using bit_not = defer; - } // namespace lazy - - /// \cond - namespace detail - { - enum class indices_strategy_ - { - done, - repeat, - recurse - }; - - constexpr indices_strategy_ strategy_(std::size_t cur, std::size_t end) - { - return cur >= end ? indices_strategy_::done - : cur * 2 <= end ? indices_strategy_::repeat - : indices_strategy_::recurse; - } - - template - constexpr std::size_t range_distance_(T begin, T end) - { - return begin <= end ? static_cast(end - begin) - : throw "The start of the integer_sequence must not be " - "greater than the end"; - } - - template - struct make_indices_ - { - using type = State; - }; - - template - struct coerce_indices_ - { - }; - } // namespace detail - /// \endcond - - /////////////////////////////////////////////////////////////////////////////////////////// - // integer_sequence -#if !META_CXX_INTEGER_SEQUENCE - /// A container for a sequence of compile-time integer constants. - /// \ingroup integral - template - struct integer_sequence - { - using value_type = T; - /// \return `sizeof...(Is)` - static constexpr std::size_t size() noexcept { return sizeof...(Is); } - }; -#endif - - /////////////////////////////////////////////////////////////////////////////////////////// - // index_sequence - /// A container for a sequence of compile-time integer constants of type - /// \c std::size_t - /// \ingroup integral - template - using index_sequence = integer_sequence; - -#if META_HAS_MAKE_INTEGER_SEQ && !defined(META_DOXYGEN_INVOKED) - // Implement make_integer_sequence and make_index_sequence with the - // __make_integer_seq builtin on compilers that provide it. (Redirect - // through decltype to workaround suspected clang bug.) - /// \cond - namespace detail - { - template - __make_integer_seq make_integer_sequence_(); - } - /// \endcond - - template - using make_integer_sequence = decltype(detail::make_integer_sequence_()); - - template - using make_index_sequence = make_integer_sequence; -#else - /// Generate \c index_sequence containing integer constants [0,1,2,...,N-1]. - /// \par Complexity - /// \f$ O(log(N)) \f$. - /// \ingroup integral - template - using make_index_sequence = - _t, detail::strategy_(1, N)>>; - - /// Generate \c integer_sequence containing integer constants [0,1,2,...,N-1]. - /// \par Complexity - /// \f$ O(log(N)) \f$. - /// \ingroup integral - template - using make_integer_sequence = - _t(N)>>>; -#endif - - /////////////////////////////////////////////////////////////////////////////////////////// - // integer_range - /// Makes the integer sequence [From, To). - /// \par Complexity - /// \f$ O(log(To - From)) \f$. - /// \ingroup integral - template - using integer_range = - _t>>; - - /// \cond - namespace detail - { - template - struct concat_indices_ - { - }; - - template - struct concat_indices_, index_sequence> - { - using type = index_sequence; - }; - - template <> - struct make_indices_<0u, index_sequence<0>, indices_strategy_::done> - { - using type = index_sequence<>; - }; - - template - struct make_indices_, indices_strategy_::repeat> - : make_indices_, - detail::strategy_(sizeof...(Values) * 2, End)> - { - }; - - template - struct make_indices_, indices_strategy_::recurse> - : concat_indices_, - make_index_sequence> - { - }; - - template - struct coerce_indices_> - { - using type = - integer_sequence(static_cast(Values) + Offset)...>; - }; - } // namespace detail - /// \endcond - - /// Evaluate the invocable \p Fn with the arguments \p Args. - /// \ingroup invocation - template - using invoke = typename Fn::template invoke; - - /// Lazy versions of meta actions - namespace lazy - { - /// \sa `meta::invoke` - /// \ingroup lazy_invocation - template - using invoke = defer; - } // namespace lazy - - /// A trait that always returns its argument \p T. It is also an invocable - /// that always returns \p T. - /// \ingroup trait - /// \ingroup invocation - template - struct id - { -#if defined(META_WORKAROUND_CWG_1558) && !defined(META_DOXYGEN_INVOKED) - // Redirect through decltype for compilers that have not - // yet implemented CWG 1558: - static id impl(void *); - - template - using invoke = _t *>(nullptr)))>; -#else - template - using invoke = T; -#endif - - using type = T; - }; - - /// An alias for type \p T. Useful in non-deduced contexts. - /// \ingroup trait - template - using id_t = _t>; - - namespace lazy - { - /// \sa `meta::id` - /// \ingroup lazy_trait - /// \ingroup lazy_invocation - template - using id = defer; - } // namespace lazy - - /// An alias for `void`. - /// \ingroup trait -#if defined(META_WORKAROUND_CWG_1558) && !defined(META_DOXYGEN_INVOKED) - // Redirect through decltype for compilers that have not - // yet implemented CWG 1558: - template - using void_ = invoke, Ts...>; -#else - template - using void_ = void; -#endif - -#if META_CXX_VARIABLE_TEMPLATES -#ifdef META_CONCEPT - /// `true` if `T::type` exists and names a type; `false` otherwise. - /// \ingroup trait - template - META_INLINE_VAR constexpr bool is_trait_v = trait; - - /// `true` if `T::invoke` exists and names a class template; `false` otherwise. - /// \ingroup trait - template - META_INLINE_VAR constexpr bool is_callable_v = invocable; -#else // ^^^ Concepts / No concepts vvv - /// \cond - namespace detail - { - template - META_INLINE_VAR constexpr bool is_trait_ = false; - - template - META_INLINE_VAR constexpr bool is_trait_> = true; - - template - META_INLINE_VAR constexpr bool is_callable_ = false; - - template - META_INLINE_VAR constexpr bool is_callable_>> = true; - } // namespace detail - /// \endcond - - /// `true` if `T::type` exists and names a type; `false` otherwise. - /// \ingroup trait - template - META_INLINE_VAR constexpr bool is_trait_v = detail::is_trait_; - - /// `true` if `T::invoke` exists and names a class template; `false` otherwise. - /// \ingroup trait - template - META_INLINE_VAR constexpr bool is_callable_v = detail::is_callable_; -#endif // Concepts vs. variable templates - - /// An alias for `std::true_type` if `T::type` exists and names a type; otherwise, it's an - /// alias for `std::false_type`. - /// \ingroup trait - template - using is_trait = bool_>; - - /// An alias for `std::true_type` if `T::invoke` exists and names a class template; - /// otherwise, it's an alias for `std::false_type`. - /// \ingroup trait - template - using is_callable = bool_>; -#else // ^^^ META_CXX_VARIABLE_TEMPLATES / !META_CXX_VARIABLE_TEMPLATES vvv - /// \cond - namespace detail - { - template - struct is_trait_ - { - using type = std::false_type; - }; - - template - struct is_trait_> - { - using type = std::true_type; - }; - - template - struct is_callable_ - { - using type = std::false_type; - }; - - template - struct is_callable_>> - { - using type = std::true_type; - }; - } // namespace detail - /// \endcond - - template - using is_trait = _t>; - - /// An alias for `std::true_type` if `T::invoke` exists and names a class - /// template or alias template; otherwise, it's an alias for - /// `std::false_type`. - /// \ingroup trait - template - using is_callable = _t>; -#endif - - /// \cond - namespace detail - { -#ifdef META_CONCEPT - template