Repository: think-cell/think-cell-library Branch: main Commit: 9b334a494d66 Files: 393 Total size: 2.7 MB Directory structure: gitextract_lxw4u0yo/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── main.yml ├── CMakeLists.txt ├── LICENSE_1_0.txt ├── README.md ├── range.example.cpp └── tc/ ├── algorithm/ │ ├── accumulate.h │ ├── algorithm.h │ ├── algorithm.t.cpp │ ├── any_accu.h │ ├── append.h │ ├── append.t.cpp │ ├── best_element.h │ ├── binary_operators.h │ ├── break_or_continue.h │ ├── compare.h │ ├── element.h │ ├── element.t.cpp │ ├── empty.h │ ├── empty.t.cpp │ ├── equal.h │ ├── equal.t.cpp │ ├── filter_inplace.h │ ├── find.h │ ├── find_closest_if.h │ ├── find_closest_if.t.cpp │ ├── for_each.h │ ├── for_each.t.cpp │ ├── for_each_xxx.h │ ├── interleave_ranges.h │ ├── longest_common_prefix.h │ ├── longest_common_prefix.t.cpp │ ├── minmax.h │ ├── partition_iterator.h │ ├── partition_range.h │ ├── quantifier.h │ ├── quantifier.t.cpp │ ├── restrict_size_decrement.h │ ├── round.h │ ├── round.t.cpp │ ├── size.h │ ├── size_bounded.h │ ├── size_linear.h │ ├── sort_streaming.h │ └── sort_streaming.t.cpp ├── array.h ├── base/ │ ├── accessors.h │ ├── as_lvalue.h │ ├── assert_defs.h │ ├── bit_cast.h │ ├── bitfield.h │ ├── bitfield.t.cpp │ ├── casts.h │ ├── casts.t.cpp │ ├── chained.h │ ├── change.h │ ├── conditional.h │ ├── conditional.t.cpp │ ├── construction_restrictiveness.h │ ├── derivable.h │ ├── empty_chain.h │ ├── enum.h │ ├── enum.t.cpp │ ├── explicit_cast.h │ ├── explicit_cast_fwd.h │ ├── functors.h │ ├── fundamental.h │ ├── generic_macros.h │ ├── has_xxx.h │ ├── inplace.h │ ├── inside_unwinding.h │ ├── integer.h │ ├── invoke.h │ ├── invoke.t.cpp │ ├── invoke_with_constant.h │ ├── large_integer.h │ ├── modified.h │ ├── modified.t.cpp │ ├── move.h │ ├── noncopyable.h │ ├── ref.h │ ├── reference_or_value.h │ ├── reference_or_value.t.cpp │ ├── renew.h │ ├── return_decltype.h │ ├── rvalue_property.h │ ├── rvalue_property.t.cpp │ ├── safe_comparison.h │ ├── safe_comparison.t.cpp │ ├── scope.h │ ├── static_polymorphism.h │ ├── string_template_param.h │ ├── tag_type.h │ ├── template_func.h │ ├── temporary.h │ ├── temporary.t.cpp │ ├── track_instance.h │ ├── track_instance.t.cpp │ ├── trivial_functors.h │ ├── trivial_functors.t.cpp │ ├── type_list.h │ ├── type_list.t.cpp │ ├── type_traits.h │ ├── type_traits.t.cpp │ ├── type_traits_fwd.h │ ├── utility.h │ └── utility.t.cpp ├── compat.t.cpp ├── const.t.cpp ├── container/ │ ├── cont_assign.h │ ├── cont_assign.t.cpp │ ├── cont_reserve.h │ ├── container.h │ ├── container_traits.h │ ├── insert.h │ └── string.h ├── create.t.cpp ├── dense_map.h ├── dense_map.natvis ├── dense_map.t.cpp ├── enumset.h ├── interval.h ├── interval.t.cpp ├── interval_types.h ├── optional.h ├── optional.t.cpp ├── range/ │ ├── adjacent_adaptor.h │ ├── cartesian_product_adaptor.h │ ├── concat_adaptor.h │ ├── concat_adaptor.t.cpp │ ├── conditional_range.h │ ├── conditional_range.t.cpp │ ├── empty_range.h │ ├── filter_adaptor.h │ ├── index_iterator.h │ ├── index_range.h │ ├── intersection_adaptor.h │ ├── intersection_adaptor.t.cpp │ ├── iota_range.h │ ├── iterator_cache.h │ ├── iterator_facade.h │ ├── join_adaptor.h │ ├── join_framed_adaptor.h │ ├── literal_range.h │ ├── literal_range.t.cpp │ ├── make_range.h │ ├── merge_range.h │ ├── merge_ranges.h │ ├── merge_ranges.t.cpp │ ├── meta.h │ ├── ordered_pairs.h │ ├── partial_sum.h │ ├── range.t.cpp │ ├── range_adaptor.h │ ├── range_return.h │ ├── repeat_n.h │ ├── reverse_adaptor.h │ ├── reverse_adaptor.t.cpp │ ├── sparse_adaptor.h │ ├── sparse_adaptor.t.cpp │ ├── subrange.h │ ├── subrange.t.cpp │ ├── take_while.h │ ├── take_while.t.cpp │ ├── transform.h │ ├── transform.t.cpp │ ├── transform_adaptor.h │ ├── union_adaptor.h │ ├── unique_range_adaptor.h │ ├── zip_range.h │ └── zip_range.t.cpp ├── restricted_enum.h ├── restricted_enum.t.cpp ├── static_vector.h ├── storage_for.h ├── string/ │ ├── ascii.h │ ├── char.h │ ├── char.t.cpp │ ├── convert_enc.h │ ├── convert_enc.t.cpp │ ├── format.h │ ├── jsonparser.h │ ├── jsonparser.t.cpp │ ├── make_c_str.h │ ├── make_c_str.t.cpp │ ├── named.h │ ├── parserbase.h │ ├── spirit/ │ │ ├── support/ │ │ │ ├── assert_msg.hpp │ │ │ ├── char_encoding/ │ │ │ │ ├── ascii.hpp │ │ │ │ ├── iso8859_1.hpp │ │ │ │ ├── standard.hpp │ │ │ │ ├── standard_wide.hpp │ │ │ │ ├── unicode/ │ │ │ │ │ ├── category_table.hpp │ │ │ │ │ ├── lowercase_table.hpp │ │ │ │ │ ├── query.hpp │ │ │ │ │ ├── script_table.hpp │ │ │ │ │ └── uppercase_table.hpp │ │ │ │ └── unicode.hpp │ │ │ └── char_set/ │ │ │ ├── basic_chset.hpp │ │ │ ├── range.hpp │ │ │ ├── range_functions.hpp │ │ │ ├── range_run.hpp │ │ │ └── range_run_impl.hpp │ │ ├── unittest/ │ │ │ ├── actions.t.cpp │ │ │ ├── alternative.t.cpp │ │ │ ├── and_predicate.t.cpp │ │ │ ├── attr.t.cpp │ │ │ ├── attribute_type_check.t.cpp │ │ │ ├── binary.t.cpp │ │ │ ├── bool.hpp │ │ │ ├── bool.t.cpp │ │ │ ├── char1.t.cpp │ │ │ ├── char_class.t.cpp │ │ │ ├── confix.t.cpp │ │ │ ├── container_support.t.cpp │ │ │ ├── difference.t.cpp │ │ │ ├── eoi.t.cpp │ │ │ ├── eol.t.cpp │ │ │ ├── eps.t.cpp │ │ │ ├── error_handler.t.cpp │ │ │ ├── expect.t.cpp │ │ │ ├── extract_int.t.cpp │ │ │ ├── fusion_map.t.cpp │ │ │ ├── grammar.hpp │ │ │ ├── grammar.t.cpp │ │ │ ├── grammar_linker.t.cpp │ │ │ ├── int.hpp │ │ │ ├── int1.t.cpp │ │ │ ├── iterator_check.t.cpp │ │ │ ├── kleene.t.cpp │ │ │ ├── lexeme.t.cpp │ │ │ ├── list.t.cpp │ │ │ ├── lit.t.cpp │ │ │ ├── lit1.t.cpp │ │ │ ├── lit2.t.cpp │ │ │ ├── matches.t.cpp │ │ │ ├── no_case.t.cpp │ │ │ ├── no_skip.t.cpp │ │ │ ├── not_predicate.t.cpp │ │ │ ├── omit.t.cpp │ │ │ ├── optional_x3.t.cpp │ │ │ ├── plus.t.cpp │ │ │ ├── raw.t.cpp │ │ │ ├── real.hpp │ │ │ ├── real1.t.cpp │ │ │ ├── real2.t.cpp │ │ │ ├── real3.t.cpp │ │ │ ├── real4.t.cpp │ │ │ ├── repeat.t.cpp │ │ │ ├── rule1.t.cpp │ │ │ ├── rule2.t.cpp │ │ │ ├── rule3.t.cpp │ │ │ ├── rule4.t.cpp │ │ │ ├── rule_separate_tu.t.cpp │ │ │ ├── rule_separate_tu_grammar.hpp │ │ │ ├── rule_separate_tu_grammar.t.cpp │ │ │ ├── seek.t.cpp │ │ │ ├── sequence.t.cpp │ │ │ ├── skip.t.cpp │ │ │ ├── symbols1.t.cpp │ │ │ ├── symbols2.t.cpp │ │ │ ├── symbols3.t.cpp │ │ │ ├── test.hpp │ │ │ ├── to_utf8.t.cpp │ │ │ ├── tst.t.cpp │ │ │ ├── uint.hpp │ │ │ ├── uint1.t.cpp │ │ │ ├── uint_radix.hpp │ │ │ ├── uint_radix.t.cpp │ │ │ ├── unused_type.t.cpp │ │ │ ├── utils.hpp │ │ │ ├── with.t.cpp │ │ │ └── x3_variant.t.cpp │ │ ├── x3/ │ │ │ ├── auxiliary/ │ │ │ │ ├── attr.hpp │ │ │ │ ├── eoi.hpp │ │ │ │ ├── eol.hpp │ │ │ │ ├── eps.hpp │ │ │ │ └── guard.hpp │ │ │ ├── auxiliary.hpp │ │ │ ├── binary/ │ │ │ │ └── binary.hpp │ │ │ ├── binary.hpp │ │ │ ├── char/ │ │ │ │ ├── any_char.hpp │ │ │ │ ├── char.hpp │ │ │ │ ├── char_class.hpp │ │ │ │ ├── char_class_tags.hpp │ │ │ │ ├── char_parser.hpp │ │ │ │ ├── char_set.hpp │ │ │ │ ├── detail/ │ │ │ │ │ └── cast_char.hpp │ │ │ │ ├── literal_char.hpp │ │ │ │ ├── negated_char_parser.hpp │ │ │ │ └── unicode.hpp │ │ │ ├── char.hpp │ │ │ ├── core/ │ │ │ │ ├── action.hpp │ │ │ │ ├── call.hpp │ │ │ │ ├── detail/ │ │ │ │ │ └── parse_into_container.hpp │ │ │ │ ├── parse.hpp │ │ │ │ ├── parser.hpp │ │ │ │ ├── proxy.hpp │ │ │ │ └── skip_over.hpp │ │ │ ├── core.hpp │ │ │ ├── directive/ │ │ │ │ ├── confix.hpp │ │ │ │ ├── expect.hpp │ │ │ │ ├── lexeme.hpp │ │ │ │ ├── matches.hpp │ │ │ │ ├── no_case.hpp │ │ │ │ ├── no_skip.hpp │ │ │ │ ├── omit.hpp │ │ │ │ ├── raw.hpp │ │ │ │ ├── repeat.hpp │ │ │ │ ├── seek.hpp │ │ │ │ ├── skip.hpp │ │ │ │ └── with.hpp │ │ │ ├── directive.hpp │ │ │ ├── nonterminal/ │ │ │ │ ├── debug_handler_state.hpp │ │ │ │ ├── detail/ │ │ │ │ │ ├── rule.hpp │ │ │ │ │ └── transform_attribute.hpp │ │ │ │ ├── rule.hpp │ │ │ │ └── simple_trace.hpp │ │ │ ├── nonterminal.hpp │ │ │ ├── numeric/ │ │ │ │ ├── bool.hpp │ │ │ │ ├── bool_policies.hpp │ │ │ │ ├── int.hpp │ │ │ │ ├── real.hpp │ │ │ │ ├── real_policies.hpp │ │ │ │ └── uint.hpp │ │ │ ├── numeric.hpp │ │ │ ├── operator/ │ │ │ │ ├── alternative.hpp │ │ │ │ ├── and_predicate.hpp │ │ │ │ ├── detail/ │ │ │ │ │ ├── alternative.hpp │ │ │ │ │ └── sequence.hpp │ │ │ │ ├── difference.hpp │ │ │ │ ├── kleene.hpp │ │ │ │ ├── list.hpp │ │ │ │ ├── not_predicate.hpp │ │ │ │ ├── optional.hpp │ │ │ │ ├── plus.hpp │ │ │ │ └── sequence.hpp │ │ │ ├── operator.hpp │ │ │ ├── string/ │ │ │ │ ├── detail/ │ │ │ │ │ ├── string_parse.hpp │ │ │ │ │ └── tst.hpp │ │ │ │ ├── literal_string.hpp │ │ │ │ ├── symbols.hpp │ │ │ │ ├── tst.hpp │ │ │ │ └── tst_map.hpp │ │ │ ├── string.hpp │ │ │ ├── support/ │ │ │ │ ├── ast/ │ │ │ │ │ ├── position_tagged.hpp │ │ │ │ │ └── variant.hpp │ │ │ │ ├── context.hpp │ │ │ │ ├── no_case.hpp │ │ │ │ ├── numeric_utils/ │ │ │ │ │ ├── detail/ │ │ │ │ │ │ └── extract_int.hpp │ │ │ │ │ ├── extract_int.hpp │ │ │ │ │ ├── extract_real.hpp │ │ │ │ │ └── pow10.hpp │ │ │ │ ├── subcontext.hpp │ │ │ │ ├── traits/ │ │ │ │ │ ├── attribute_category.hpp │ │ │ │ │ ├── attribute_of.hpp │ │ │ │ │ ├── attribute_of_binary.hpp │ │ │ │ │ ├── attribute_type.hpp │ │ │ │ │ ├── container_traits.hpp │ │ │ │ │ ├── handles_container.hpp │ │ │ │ │ ├── has_attribute.hpp │ │ │ │ │ ├── is_range.hpp │ │ │ │ │ ├── is_substitute.hpp │ │ │ │ │ ├── is_variant.hpp │ │ │ │ │ ├── move_to.hpp │ │ │ │ │ ├── numeric_traits.hpp │ │ │ │ │ ├── optional_traits.hpp │ │ │ │ │ ├── print_attribute.hpp │ │ │ │ │ ├── print_token.hpp │ │ │ │ │ ├── pseudo_attribute.hpp │ │ │ │ │ ├── string_traits.hpp │ │ │ │ │ ├── transform_attribute.hpp │ │ │ │ │ ├── tuple_traits.hpp │ │ │ │ │ ├── variant_find_substitute.hpp │ │ │ │ │ └── variant_has_substitute.hpp │ │ │ │ ├── unused.hpp │ │ │ │ └── utility/ │ │ │ │ ├── annotate_on_success.hpp │ │ │ │ ├── error_reporting.hpp │ │ │ │ ├── is_callable.hpp │ │ │ │ ├── lambda_visitor.hpp │ │ │ │ ├── sfinae.hpp │ │ │ │ └── utf8.hpp │ │ │ └── version.hpp │ │ └── x3.hpp │ ├── spirit.h │ ├── spirit_algorithm.h │ ├── xmlparser.h │ ├── xmlparser.t.cpp │ └── xmltransform.h ├── tuple.h ├── unittest.h ├── variant.h └── variant.t.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * text=auto ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: [push, pull_request] jobs: build: strategy: fail-fast: false matrix: image: ["gcc:13", "clang:16"] config: [Debug, Release] runs-on: ubuntu-latest container: image: ghcr.io/think-cell/${{matrix.image}} steps: - uses: actions/checkout@v4 - name: Setup Boost shell: bash run: | curl -L https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.zip --output boost_1_83_0.zip unzip -q boost_1_83_0.zip mv boost_1_83_0/ boost/ - name: CMake configure run: cmake -S . -B build -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DBoost_INCLUDE_DIR=$GITHUB_WORKSPACE/boost/ -DBoost_LIBRARY_DIR=$GITHUB_WORKSPACE/boost/ - name: Compile run: cmake --build build - name: Run run: cmake --build build --target test ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.21) project( tc_library_test VERSION 1.0 LANGUAGES C CXX ) set(CMAKE_CXX_STANDARD 20) # cmake -S . -B build -DBoost_INCLUDE_DIR=C:\Libraries\boost_1_79_0/ -DBoost_LIBRARY_DIR=C:\Libraries\boost_1_79_0/ find_package(Boost 1.75 REQUIRED) # Generate one cpp file per header file( GLOB_RECURSE liststrLibrary LIST_DIRECTORIES false CONFIGURE_DEPENDS RELATIVE ${CMAKE_SOURCE_DIR}/tc ${CMAKE_SOURCE_DIR}/tc/*.h ) set(liststrLibraryCpp "") foreach(strLibrary ${liststrLibrary}) set(strLibraryCpp ${CMAKE_BINARY_DIR}/test/${strLibrary}.cpp) list(APPEND liststrLibraryCpp ${strLibraryCpp}) file(WRITE ${strLibraryCpp} "#include " \"${strLibrary}\" ) endforeach() file(WRITE ${CMAKE_BINARY_DIR}/test/main.cpp "int main() { return 0; }" ) # Test if each header compiles individually add_executable(compile_test ${liststrLibraryCpp} ${CMAKE_BINARY_DIR}/test/main.cpp) target_include_directories(compile_test PRIVATE ${CMAKE_SOURCE_DIR}/tc) target_link_libraries(compile_test Boost::boost Boost::disable_autolinking) # Run unit tests include(CTest) list(APPEND CMAKE_CTEST_ARGUMENTS "--verbose") file( GLOB_RECURSE liststrUnitTestFiles LIST_DIRECTORIES false CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tc/*.t.cpp ) add_executable(unit_test ${liststrUnitTestFiles} ${CMAKE_BINARY_DIR}/test/main.cpp) target_include_directories(unit_test PRIVATE ${CMAKE_SOURCE_DIR}/tc) target_link_libraries(unit_test Boost::boost Boost::disable_autolinking) add_test(NAME unit_test COMMAND unit_test) add_executable(example_test range.example.cpp) target_link_libraries(example_test Boost::boost Boost::disable_autolinking) add_test(NAME example_test COMMAND example_test) ================================================ FILE: LICENSE_1_0.txt ================================================ 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. ================================================ FILE: README.md ================================================ [![CI](https://github.com/think-cell/range/actions/workflows/main.yml/badge.svg)](https://github.com/think-cell/range/actions/workflows/main.yml) think-cell public library ========================= This library consists of several core C++ utilities that we at *think-cell Software* have developed and consider to be useful. It mostly covers ranges, but you will see that it also contains other handy features. We continuously improve our library as part of our daily work, and whenever we gain new insights, we add them as we go. To get in touch with other programmers, we regularly [give talks about our ideas](https://www.think-cell.com/career/talks/overview.shtml), using this library as a reference. We consider the library to be production-quality code, but it is important to know that we do not strive for stable interfaces. Being free of such constraints is an important requirement for further enhancements. Clean and expressive code makes reasoning about it - and thus further progress - easier. Therefore, we adopt the latest language features quickly if it helps the case. This library has been made publicly available as an example of modern C++ coding techniques and as a source of inspiration for other library writers. And of course because we are proud of it! The documentation is currently lacking and we are working on that. We will publish usage examples on our new [developer blog](https://www.think-cell.com/devblog) and will merge them into the library as comments. ------------- Contributions ------------- If you propose a change that improves correctness or standard-conformance, we encourage you to make a pull request. But please understand that, for the above-mentioned reasons, we are not keen on workarounds to accommodate outdated compilers. Does hacking our library give you a kick, and do you think you can contribute more? We are a friendly and driven bunch of C++ enthusiasts with a knack for elegant algorithms, and we are always looking for [new colleagues](https://www.think-cell.com/career). ------------------ Usage instructions ------------------ You need to have boost installed (we tested with [1.75.0](https://www.boost.org/users/history/version_1_75_0.html)) and the following compiler settings: ##### Visual C++ 19.28 (Visual Studio 2019 16.9) * `/std:c++latest` ##### clang (*13.0.0* and *Apple LLVM 14.0.0*) * `-std=c++2a` ##### gcc 12 * `-std=c++2a` `range.example.cpp` provides a good entry point to get started quickly. If you want to see more examples, there are some unit tests in `tc/*.t.cpp`. ================================================ FILE: range.example.cpp ================================================ // think-cell public library // // Copyright (C) 2016-2018 think-cell Software GmbH // // Distributed under 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 #include "tc/range/meta.h" #include "tc/range/filter_adaptor.h" #include "tc/string/format.h" #include "tc/string/make_c_str.h" #include #include #include namespace { template void print(Args&&... args) noexcept { std::printf("%s", tc::implicit_cast(tc::make_c_str(std::forward(args)...))); } //---- Basic ------------------------------------------------------------------------------------------------------------------ void basic () { std::vector v = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; tc::for_each( tc::filter(v, [](const int& n){ return (n%2==0);}), [&](auto const& n) { print(tc::as_dec(n), ", "); } ); print("\n"); } //---- Generator Range -------------------------------------------------------------------------------------------------------- namespace { struct generator_range { template< typename Func > void operator()( Func func ) const& { for(int i=0;i<50;++i) { func(i); } } }; } void ex_generator_range () { tc::for_each( tc::filter( generator_range(), [](int i){ return i%2==0; } ), [](int i) { print(tc::as_dec(i), ", "); }); print("\n"); } //---- Generator Range (with break) ------------------------------------------------------------------------------------------- namespace { struct generator_range_break { template< typename Func > tc::break_or_continue operator()( Func func ) const& { using namespace tc; for(int i=0;i<5000;++i) { if (func(i)==break_) { return break_; } } return continue_; } }; } void ex_generator_range_break () { tc::for_each( tc::filter( generator_range_break(), [](int i){ return i%2==0; } ), [](int i) -> tc::break_or_continue { print(tc::as_dec(i), ", "); return (i>=50)? tc::break_ : tc::continue_; }); print("\n"); } //---- Stacked filters -------------------------------------------------------------------------------------------------------- void stacked_filters() { tc::for_each( tc::filter( tc::filter( tc::filter( generator_range_break(), [](int i){ return i%2!=0; } ), [](int i){ return i%3!=0; } ), [](int i){ return i%5!=0; } ) , [](int i) -> tc::break_or_continue { print(tc::as_dec(i), ", "); return (i>25)? tc::break_ : tc::continue_; }); print("\n"); } } int main() { print("-- Running Examples ----------\n"); basic(); ex_generator_range(); ex_generator_range_break(); stacked_filters(); using namespace tc; int av[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; auto v = std::vector (av, av+sizeof(av)/sizeof(int)); //---- filter example with iterators ------------------------------------------- auto r = tc::filter( tc::filter( tc::filter( v, [](int i){ return i%2!=0; } ), [](int i){ return i%3!=0; } ), [](int i){ return i%5!=0; } ); for (auto it = std::begin(r), end = std::end(r); it != end; ++it) { print(tc::as_dec(*it), ", "); } print("\n"); //---- boost for comparison ----------------------------------------------------- auto br = v | boost::adaptors::filtered([](int i){ return i%2!=0; }) | boost::adaptors::filtered([](int i){ return i%3!=0; }) | boost::adaptors::filtered([](int i){ return i%5!=0; }); for (auto it = std::begin(br), end = std::end(br); it != end; ++it) { print(tc::as_dec(*it), ", "); } print("\n"); print("-- Done ----------\n"); std::fflush(stdout); return EXIT_SUCCESS; } ================================================ FILE: tc/algorithm/accumulate.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "for_each.h" #include "../base/modified.h" #include "../optional.h" namespace tc { ///////////////////////////////////////////////////// // accumulate namespace no_adl { template< typename T, typename AccuOp > struct accumulate_fn /*final*/ { T * m_pt; AccuOp * m_paccuop; constexpr accumulate_fn( T & t, AccuOp & accuop ) noexcept : m_pt(std::addressof(t)), m_paccuop(std::addressof(accuop)) {} template constexpr auto operator()(S&& s) const& MAYTHROW { return tc::continue_if_not_break(*m_paccuop, *m_pt, tc_move_if_owned(s)); } }; } template< typename Rng, typename T, typename AccuOp > [[nodiscard]] constexpr T accumulate(Rng&& rng, T t, AccuOp accuop) MAYTHROW { tc::for_each(tc_move_if_owned(rng), no_adl::accumulate_fn(t,accuop)); return t; } namespace no_adl { template< typename T, typename AccuOp > struct [[nodiscard]] accumulator_with_front /*final*/ { std::optional & m_t; AccuOp m_accuop; template< typename S > constexpr auto operator()( S&& s ) const& MAYTHROW { return tc_conditional_prvalue_as_val( m_t, tc::continue_if_not_break( m_accuop, *m_t, tc_move_if_owned(s) ), TC_FWD(tc::optional_emplace(m_t, tc_move_if_owned(s)), tc::constant()) ); } }; } template constexpr no_adl::accumulator_with_front make_accumulator_with_front(std::optional& value, AccuOp&& accumulate) noexcept { return {value, tc_move_if_owned(accumulate)}; } template [[nodiscard]] constexpr std::optional accumulate_with_front(Rng&& rng, AccuOp&& accuop) MAYTHROW { static_assert(tc::decayed); std::optional t; tc::for_each(tc_move_if_owned(rng), tc::make_accumulator_with_front(t, tc_move_if_owned(accuop))); return t; } template [[nodiscard]] constexpr auto accumulate_with_front(Rng&& rng, AccuOp&& accuop) MAYTHROW { return tc::accumulate_with_front>(tc_move_if_owned(rng), tc_move_if_owned(accuop)); } } ================================================ FILE: tc/algorithm/algorithm.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../base/functors.h" #include "../base/tag_type.h" #include "../range/adjacent_adaptor.h" #include "../range/make_range.h" #include "../range/meta.h" #include "../range/join_framed_adaptor.h" #include "../range/subrange.h" #include "../range/unique_range_adaptor.h" #include "../range/iota_range.h" #include "../range/concat_adaptor.h" #include "../range/filter_adaptor.h" #include "../container/container_traits.h" #include "../container/container.h" // tc::vector #include "../container/cont_assign.h" #include "../container/insert.h" #include "../container/cont_reserve.h" #include "../storage_for.h" #include "../string/spirit.h" #include "append.h" #include "size.h" #include "element.h" #include "equal.h" #include "quantifier.h" #include "empty.h" #include "minmax.h" #include "compare.h" #include "for_each_xxx.h" #include "accumulate.h" #include "size_bounded.h" #include "find.h" #include "filter_inplace.h" #include "best_element.h" #include "partition_iterator.h" #include "partition_range.h" #include "size_linear.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace tc { template< typename Rng, typename Less = tc::fn_less> [[nodiscard]] constexpr bool is_strictly_sorted(Rng const& rng, Less less = Less()) noexcept { return tc::all_of(tc::adjacent<2>(rng), [&](auto const& first, auto const& second) noexcept { return less(first, second); }); } template< typename Rng, typename Less = tc::fn_less> [[nodiscard]] constexpr bool is_sorted(Rng const& rng, Less less = Less()) MAYTHROW { return !tc::any_of(tc::adjacent<2>(rng), [&](auto const& first, auto const& second) MAYTHROW { return less(second, first); // MAYTHROW }); } ///////////////////////////////// // associative containers namespace explicit_convert_adl { template requires has_mem_fn_lower_bound || has_mem_fn_hash_function TTarget explicit_convert_impl(adl_tag_t, std::type_identity, Rng&& rng) noexcept { TTarget cont; // force each element to be inserted tc::cont_must_insert_range(cont, tc_move_if_owned(rng)); return cont; } } ///////////////////////////////////////////////////// // sort #ifdef __clang__ namespace constexpr_sort_inplace_detail { // This duplicates the functionality of tc::sort, which cannot be used in a constant expression, // because it uses std::sort which is not constexpr (until C++20). template constexpr void constexpr_sort_inplace(Iterator itBegin, Iterator itEnd, Pred pred) noexcept { #if defined(_CHECKS) && defined(TC_PRIVATE) int nIterationCount = 0; #endif while (1 < itEnd - itBegin) { _ASSERTENOTIFY( ++nIterationCount <= 32 ); // Do we actually run into O(n^2) complexity? auto itPartitionBegin = itBegin; auto itPartitionEnd = tc_modified(itEnd, --_); // Any iterator in the interval [itBegin, itEnd - 2] works. // Middle is best for sorted and reverse sorted ranges. auto itPivotElement = tc::middle_point(itBegin, itPartitionEnd); for (;;) { while (pred(*itPartitionBegin, *itPivotElement)) { ++itPartitionBegin; } while (pred(*itPivotElement, *itPartitionEnd)) { --itPartitionEnd; } if (itPartitionEnd <= itPartitionBegin) { break; } if (itPartitionBegin == itPivotElement) { itPivotElement = itPartitionEnd; } else if (itPartitionEnd == itPivotElement) { itPivotElement = itPartitionBegin; } tc::swap(*itPartitionBegin, *itPartitionEnd); ++itPartitionBegin; --itPartitionEnd; } auto const itSplitPoint = itPartitionEnd + 1; #if defined(_CHECKS) && defined(_DEBUG) _ASSERTE(itBegin < itSplitPoint); _ASSERTE(itSplitPoint < itEnd); for (auto j = itBegin; j < itSplitPoint; ++j) { _ASSERTE(!pred(*itPivotElement, *j)); } for (auto j = itSplitPoint; j < itEnd; ++j) { _ASSERTE(!pred(*j, *itPivotElement)); } #endif // Recur into smaller subrange and sort larger subrange in next iteration to get O(log n) space complexity. if( itSplitPoint - itBegin < itEnd - itSplitPoint ) { constexpr_sort_inplace_detail::constexpr_sort_inplace(itBegin, itSplitPoint, pred); itBegin = itSplitPoint; } else { constexpr_sort_inplace_detail::constexpr_sort_inplace(itSplitPoint, itEnd, pred); itEnd = itSplitPoint; } } } } #endif template constexpr void sort_inplace(Rng&& rng, Less&& less = Less()) noexcept(noexcept(less(tc::front(rng), tc::front(rng)))) { if constexpr( has_mem_fn_sort< Rng >) { static_assert( std::is_lvalue_reference::value ); rng.sort( tc_move_if_owned(less) ); } else { #ifdef __clang__ // xcode12 does not support constexpr std::sort if(std::is_constant_evaluated()) { constexpr_sort_inplace_detail::constexpr_sort_inplace(tc::begin(rng), tc::end(rng), less); _ASSERTE( tc::is_sorted(rng, less) ); } else { #endif std::sort( tc::begin(rng), tc::end(rng), tc_move_if_owned(less) ); #ifdef __clang__ } #endif } } template void stable_sort_inplace(Rng&& rng, Less&& less = Less()) noexcept(noexcept(less(tc::front(rng), tc::front(rng)))) { std::stable_sort(tc::begin(rng), tc::end(rng), tc_move_if_owned(less)); } template< typename Less=tc::fn_less > constexpr void strictly_sort_inplace(auto&& rng, Less less=Less()) noexcept(noexcept(less(tc::front(rng), tc::front(rng)))) { tc::sort_inplace(rng, less); _ASSERT( tc::is_strictly_sorted(rng, less) ); } namespace no_adl { template struct [[nodiscard]] sorted_index_adaptor final : tc::range_adaptor_base_range , tc::index_range_adaptor< sorted_index_adaptor, tc::vector>>, index_range_adaptor_flags::inherit_begin_end | index_range_adaptor_flags::inherit_traversal > , private tc::nonmovable // disable copy ctor and default move ctor { private: using this_type = sorted_index_adaptor; using base_range_base = tc::range_adaptor_base_range; using index_base = typename this_type::index_range_adaptor; public: using base_range_base::base_range; using typename index_base::tc_index; static constexpr bool c_bHasStashingIndex=false; template explicit sorted_index_adaptor(Rng&& rng, LessOrComp lessorcomp) noexcept : base_range_base(tc::aggregate_tag, tc_move_if_owned(rng)) , index_base(tc::aggregate_tag, tc::explicit_cast>>>( tc::transform(tc::make_range_of_iterators(base_range()), tc_fn(tc::iterator2index)) )) { tc::sort_inplace( this->index_base::base_range(), [&](auto const& idxLhs, auto const& idxRhs ) noexcept -> bool { tc_auto_cref(lhs, tc::dereference_index(base_range(), idxLhs)); tc_auto_cref(rhs, tc::dereference_index(base_range(), idxRhs)); if constexpr (bStable) { static_assert(tc::random_access_range); tc_auto_cref(order, lessorcomp(lhs, rhs)); static_assert(tc::is_comparison_category>); if(tc::is_eq(order)) { return 0 // tc::reference_or_value is movable for const Rng as well : base_range_base(tc::base_cast(tc_move(src))) , index_base(tc::base_cast(tc_move(src))) {} private: STATIC_FINAL(dereference_index)(tc_index const& idx) const& return_decltype_MAYTHROW( tc::dereference_index(base_range(), *idx) ) STATIC_FINAL(dereference_index)(tc_index const& idx) & return_decltype_MAYTHROW( tc::dereference_index(base_range(), *idx) ) public: static decltype(auto) element_base_index(tc_index const& idx) noexcept { return *idx; } static decltype(auto) element_base_index(tc_index&& idx) noexcept { return *tc_move(idx); } constexpr decltype(auto) dereference_untransform(tc_index const& idx) const& noexcept { return base_range().dereference_untransform(*idx); } }; } using no_adl::sorted_index_adaptor; template [[nodiscard]] auto sorted_iterator_range(Rng& rng, Less&& less = Less()) noexcept { auto vecitSorted=tc::make_vector( tc::make_range_of_iterators(rng) ); tc::sort_inplace(vecitSorted, tc::projected( tc_move_if_owned(less), tc::fn_indirection() ) ); return vecitSorted; } template [[nodiscard]] auto sort(Rng&& rng, Less&& less = Less()) noexcept { return tc::sorted_index_adaptor(tc_move_if_owned(rng), tc_move_if_owned(less)); } template [[nodiscard]] constexpr auto constexpr_sort(Rng&& rng, Less&& less = Less()) noexcept { auto a = tc::make_array(tc_move_if_owned(rng)); tc::sort_inplace(a, tc_move_if_owned(less)); return a; } template [[nodiscard]] auto stable_sort(Rng&& rng, Comp&& comp = Comp()) noexcept { return tc::sorted_index_adaptor(tc_move_if_owned(rng), tc_move_if_owned(comp)); } namespace no_adl { template< typename Rng > struct [[nodiscard]] untransform_adaptor : tc::index_range_adaptor< untransform_adaptor, Rng, tc::index_range_adaptor_flags::inherit_begin_end | tc::index_range_adaptor_flags::inherit_traversal > { private: using this_type = untransform_adaptor; using base_ = typename untransform_adaptor::index_range_adaptor; public: using typename base_::tc_index; using base_::base_; STATIC_FINAL_MOD(template constexpr, dereference_index)(Index&& idx) & return_decltype_MAYTHROW( this->base_range().dereference_untransform(tc_move_if_owned(idx)) ) STATIC_FINAL_MOD(template constexpr, dereference_index)(Index&& idx) const& return_decltype_MAYTHROW( this->base_range().dereference_untransform(tc_move_if_owned(idx)) ) }; } template [[nodiscard]] auto untransform(Rng&& rng) noexcept { return no_adl::untransform_adaptor(tc::aggregate_tag, tc_move_if_owned(rng)); } /////////////////////////////////////// // partition ranges into subranges template [[nodiscard]] auto ordered_unique_range(Rng&& rng, Less&& less = Less()) noexcept { _ASSERTDEBUG( tc::is_sorted( rng, less ) ); return tc::adjacent_unique_range( tc_move_if_owned(rng), std::not_fn( tc_move_if_owned(less) ) ); } template auto generator_ordered_unique_range(Rng&& rng, FuncRngStart funcStart, FuncRngElement funcElem, Less&& less = Less()) noexcept { std::optional> oelem; tc::for_each(tc_move_if_owned(rng), [&](auto&& element) noexcept { if (!oelem || less(*oelem, element)) { tc_return_if_break(tc::continue_if_not_break(funcStart)) tc::optional_emplace(oelem, element); } return tc::continue_if_not_break(funcElem,tc_move_if_owned(element)); }); } template [[nodiscard]] auto stable_sort_unique_range(Rng&& rng, Comp const& comp = Comp()) noexcept { return tc::ordered_unique_range( tc::stable_sort( tc_move_if_owned(rng), comp ), tc::lessfrom3way(comp) ); } template [[nodiscard]] auto sort_unique_range(Rng&& rng, Less const& less = Less()) noexcept { return tc::ordered_unique_range( tc::sort( tc_move_if_owned(rng), less ), less ); } template [[nodiscard]] auto sort_inplace_unique_range(Rng&& rng, Less&& less = Less()) noexcept { static_assert( !std::is_reference::value ); tc::sort_inplace( rng, std::ref(less) ); return tc::ordered_unique_range( tc_move_if_owned(rng), tc_move_if_owned(less) ); } template< typename Rng, typename Less, typename Accu > void accumulate_each_unique_range(Rng&& cont, Less less, Accu accu) noexcept { range_filter< tc::decay_t > rngfilter( cont ); tc::for_each( tc::ordered_unique_range( cont, tc_move(less) ), [&accu,&rngfilter]( auto const& rngEqualSubRange ) noexcept { for( auto it=tc::begin_next(rngEqualSubRange); it!=tc::end(rngEqualSubRange); ++it ) { tc_invoke(accu,*tc::begin(rngEqualSubRange), *it); } rngfilter.keep( tc::begin(rngEqualSubRange) ); } ); } template< typename Rng, typename Less, typename Accu > void sort_accumulate_each_unique_range(Rng&& cont, Less less, Accu accu) noexcept { tc::sort_inplace( cont, less ); tc::accumulate_each_unique_range(cont, tc_move(less), tc_move(accu)); } template< typename Cont, typename Equals = decltype(tc::equal_to)> void front_unique_inplace(Cont& cont, Equals&& pred={}) noexcept(noexcept(pred(tc::front(cont), tc::front(cont)))) { tc::range_filter< tc::decay_t > rngfilter(cont); tc::for_each( tc::transform( tc::front_unique_range(cont, tc_move_if_owned(pred)), tc_fn(tc::begin) ), [&](auto it) noexcept { rngfilter.keep(tc_move(it)); } ); } /* In contrast to std::unique, tc::adjacent_unique / tc::adjacent_unique_inplace always compares adjacent elements. This allows implementing bidirectional tc::adjacent_unique, with tc::adjacent_unique_inplace yielding the same result. */ template< typename Cont, typename Equals=decltype(tc::equal_to)> constexpr void adjacent_unique_inplace(Cont & cont, Equals&& pred={}) noexcept(noexcept(pred(tc::front(cont), tc::front(cont)))) { tc::range_filter< tc::decay_t > rngfilter(cont); // When this function is evaluated at compile time, the range returned by tc::make_range_of_iterators cannot use an rvalue as a base range. // This is because it stores the base range in a mutable field inside tc::reference_or_value. tc::for_each( tc::may_remove_current(tc::make_range_of_iterators(tc::as_lvalue(tc::adjacent_unique(cont, tc_move_if_owned(pred/*MAYTHROW*/))))), [&](auto it) noexcept { rngfilter.keep(it.element_base()); } ); } template constexpr void ordered_unique_inplace( Cont& cont, Less less=Less() ) noexcept(noexcept(less(tc::front(cont), tc::front(cont)))) { _ASSERTDEBUG( tc::is_sorted( cont, less ) ); tc::adjacent_unique_inplace( cont, std::not_fn( tc_move(less) ) ); } template< typename Cont, typename Less=tc::fn_less > constexpr void sort_unique_inplace(Cont& cont, Less less=Less()) noexcept(noexcept(less(tc::front(cont), tc::front(cont)))) { tc::sort_inplace( cont, less ); tc::ordered_unique_inplace( cont, tc_move(less) ); } template< typename Cont, typename Less=tc::fn_less > void stable_sort_unique_inplace(Cont& cont, Less less=Less()) noexcept(noexcept(less(tc::front(cont), tc::front(cont)))) { tc::stable_sort_inplace( cont, less ); tc::ordered_unique_inplace( cont, tc_move(less) ); } template auto ordered_unique_begin_and_count(Rng&& rng, Less&& less) noexcept { return tc::transform(tc::ordered_unique_range( tc_move_if_owned(rng), tc_move_if_owned(less)), [&](auto const& rngSub) noexcept { return std::make_pair( tc::begin(rngSub), tc::implicit_cast >::type >(tc::size_linear(rngSub)) ); }); } template [[nodiscard]] auto plurality_element(Rng&& rng) noexcept { auto vecitSorted = tc::sorted_iterator_range(rng, tc::fn_less()); // do not inline, oit->first points into vecitSorted if(auto oit = tc::max_element( tc::ordered_unique_begin_and_count( vecitSorted, tc::projected(tc::fn_less(), fn_indirection()) ), tc_member(.second) )) { return RangeReturn::pack_element(tc_move_always(*oit->first), tc_move_if_owned(rng)); } else { return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } } template< typename RangeReturn, typename Rng, typename Pred > [[nodiscard]] decltype(auto) trim_left_if(Rng&& rng, Pred&& pred) MAYTHROW { static_assert( RangeReturn::allowed_if_always_has_border ); return tc_rewrap_temporary(Rng&&, RangeReturn::pack_border( tc::find_first_if( tc_unwrap_temporary(rng), std::not_fn(tc_move_if_owned(pred)) ), tc_unwrap_temporary(tc_move_if_owned(rng)) )); } template< typename RangeReturn, typename Rng, typename Pred > [[nodiscard]] decltype(auto) trim_right_if(Rng&& rng, Pred&& pred) MAYTHROW { static_assert( RangeReturn::allowed_if_always_has_border ); return tc_rewrap_temporary(Rng&&, RangeReturn::pack_border( tc::find_last_if( tc_unwrap_temporary(rng), std::not_fn(tc_move_if_owned(pred)) ), tc_unwrap_temporary(tc_move_if_owned(rng)) )); } template< typename Rng, typename Pred > [[nodiscard]] decltype(auto) trim_if(Rng&& rng, Pred&& pred) MAYTHROW { return tc_invoke(tc::chained( // clang crashes if we use return_decltype_allow_xvalue [&](auto&& rng) -> decltype(auto) { return tc::trim_left_if(tc_move_if_owned(rng), tc_move_if_owned(pred)); }, [&](auto&& rng) -> decltype(auto) { return tc::trim_right_if(tc_move_if_owned(rng), pred); } ), tc_move_if_owned(rng)); } template< typename RangeReturn, typename Rng, typename RngTrim > [[nodiscard]] decltype(auto) trim_left(Rng&& rng, RngTrim const& rngTrim) MAYTHROW { return tc::trim_left_if( tc_move_if_owned(rng), [&](auto const& _) noexcept { return tc::find_first(rngTrim, _); } ); } template< typename RangeReturn, typename Rng, typename RngTrim > [[nodiscard]] decltype(auto) trim_right(Rng&& rng, RngTrim const& rngTrim) MAYTHROW { return tc::trim_right_if( tc_move_if_owned(rng), [&](auto const& _) noexcept { return tc::find_first(rngTrim, _); } ); } template< typename Rng, typename RngTrim > [[nodiscard]] decltype(auto) trim(Rng&& rng, RngTrim const& rngTrim) MAYTHROW { return tc::trim_if( tc_move_if_owned(rng), [&](auto const& _) noexcept { return tc::find_first(rngTrim, _); } ); } template< typename Rng, typename T > [[nodiscard]] bool contains_single(Rng const& rng, T const& t) noexcept { return 1==size_bounded(rng,2) && tc::front( rng )==t; } namespace cont_find_detail { template< typename RangeReturn, typename Cont> decltype(auto) cont_find_impl(Cont& cont, tc::iterator_t< Cont > it) noexcept { if( it==tc::end(cont) ) { return RangeReturn::pack_no_element( cont ); } else { return RangeReturn::pack_element( it, cont ); } } } template< typename RangeReturn, typename Cont, typename Arg > [[nodiscard]] decltype(auto) cont_find(Cont& cont, Arg&& arg) noexcept { return cont_find_detail::cont_find_impl(cont, cont.find(tc_move_if_owned(arg))); } #ifdef _DEBUG using static_vector_size_t = std::uint32_t; // fixed width integer for shared heap namespace static_vector_adl { template< typename T, tc::static_vector_size_t N> struct static_vector; } using static_vector_adl::static_vector; #endif template void assert_no_null_terminator(Rng const& rng) noexcept { if constexpr( tc::char_type< tc::range_value_t > ) { _ASSERT( !tc::find_first(rng, tc::explicit_cast>('\0') )); } } template void remove_null_terminator(Rng& rng) noexcept { static_assert( tc::char_type< tc::range_value_t > ); _ASSERTEQUAL( tc::back(rng), tc::explicit_cast< tc::range_value_t >('\0') ); tc::drop_last_inplace(rng); tc::assert_no_null_terminator(rng); } template [[nodiscard]] constexpr Char const* take_null_terminated(Char const (&ach)[N]) noexcept { static_assert( tc::char_type ); _ASSERTDEBUG( tc::find_first(tc::as_array(ach), Char()) ); return ach; } namespace get_buffer_detail { #if defined(TC_PRIVATE) && defined(_DEBUG) && !defined(__clang__) namespace no_adl { template struct container_with_sentinel final { using type = Cont; }; template struct container_with_sentinel> final { using type = tc::static_vector; }; } #endif /* Calls func(pBuffer, nBufferSize) with buffers of increasing size; func may read into [pBuffer, pBuffer+nBufferSize) and return nSize, which is either the correct size of the buffer (if nSize <= nBufferSize) or an estimate otherwise. */ template void append_buffer_allowing_nulls(Cont& cont, Func func) MAYTHROW { auto const nOffset = tc::size(cont); #if defined(TC_PRIVATE) && defined(_DEBUG) && !defined(__clang__) tc::cont_reserve(cont, tc::size(cont)+1); #endif for (;;) { tc::cont_extend(cont, cont.capacity(), boost::container::default_init); // Allow func to use the whole allocated buffer // sentinel to detect buffer overrun IF_NO_MSVC_WORKAROUND(static) constexpr typename boost::range_size::type nSentinel= // workaround MSVC compiler bug: https://developercommunity.visualstudio.com/t/code-generation-bug-on-static-variable-i/10541326 #if defined(TC_PRIVATE) && defined(_DEBUG) && !defined(__clang__) 1; _ASSERT(nOffset+nSentinel <= tc::size(cont)); tc::for_each(tc::begin_next(cont, nOffset), tc_fn(UNINITIALIZED)); #else 0; #endif auto const nNeededBufferSize = func(tc::ptr_begin(cont)+nOffset, tc::size(cont)-nOffset-nSentinel); #if defined(TC_PRIVATE) && defined(_DEBUG) && !defined(__clang__) tc::assert_uninitialized(tc::back(cont)); #endif auto const nSize = nOffset + nNeededBufferSize; if (nSize+nSentinel <= tc::size(cont)) { _ASSERT(0 <= nSize); tc::take_first_inplace(cont, nSize); IF_TC_CHECKS(if (!tc::empty(cont)) _ASSERTINITIALIZED(tc::back(cont));) return; } tc::take_first_inplace(cont, nOffset); tc::cont_reserve(cont, nSize+nSentinel); // The container must grow bigger, but let cont_reserve decide by how much } } /* Calls func(pBuffer, nBufferSize) with buffers of increasing size; func may read into [pBuffer, pBuffer+nBufferSize) and return nSize, which is either the correct size of the buffer (if nSize <= nBufferSize) or an estimate otherwise. */ template Cont get_buffer_allowing_nulls(Func&& func) MAYTHROW { static_assert(tc::decayed); // sentinel to detect buffer overrun #if defined(TC_PRIVATE) && defined(_DEBUG) && !defined(__clang__) typename no_adl::container_with_sentinel::type #else Cont #endif cont; if (0 == cont.capacity()) { tc::cont_reserve(cont, 8); } append_buffer_allowing_nulls(cont, tc_move_if_owned(func)); if constexpr (std::is_same::value) { return cont; } else { tc_return_cast( tc_move(cont) ); } } template Cont get_truncating_buffer_allowing_nulls(Func func) noexcept { return tc::get_buffer_detail::get_buffer_allowing_nulls([&](auto pBuffer, auto const nBufferSize) noexcept { auto nSize = func(pBuffer, nBufferSize); if (nSize == nBufferSize) { return nSize+1; // Any return value larger than nBufferSize causes a retry with a larger buffer } else { _ASSERT(nSize < nBufferSize); return nSize; } }); } } template [[nodiscard]] Cont get_truncating_buffer(Func&& func) noexcept { auto cont=tc::get_buffer_detail::get_truncating_buffer_allowing_nulls(tc_move_if_owned(func)); tc::assert_no_null_terminator(cont); return cont; } template [[nodiscard]] Cont get_truncating_null_terminated_buffer(Func&& func) noexcept { auto cont=tc::get_buffer_detail::get_truncating_buffer_allowing_nulls(tc_move_if_owned(func)); tc::remove_null_terminator(cont); return cont; } template [[nodiscard]] Cont get_buffer(Func&& func) MAYTHROW { auto cont=tc::get_buffer_detail::get_buffer_allowing_nulls(tc_move_if_owned(func)); // MAYTHROW tc::assert_no_null_terminator(cont); return cont; } template void append_buffer(Cont& cont, Func&& func) MAYTHROW { IF_TC_CHECKS(auto const nOffset = tc::size(cont)); tc::get_buffer_detail::append_buffer_allowing_nulls(cont, tc_move_if_owned(func)); // MAYTHROW IF_TC_CHECKS(tc::assert_no_null_terminator(tc::begin_next(cont, nOffset))); } template [[nodiscard]] Cont get_null_terminated_buffer(Func&& func) MAYTHROW { auto cont=tc::get_buffer_detail::get_buffer_allowing_nulls(tc_move_if_owned(func)); // MAYTHROW static_assert( tc::char_type< tc::range_value_t > ); tc::remove_null_terminator(cont); return cont; } template [[nodiscard]] Cont get_buffer_may_be_null_terminated(Func&& func) MAYTHROW { auto cont = tc::get_buffer_detail::get_buffer_allowing_nulls(tc_move_if_owned(func)); // MAYTHROW static_assert( tc::char_type< tc::range_value_t > ); tc::take_inplace(cont, tc::ends_with(cont, tc::single(tc::explicit_cast< tc::range_value_t >('\0')))); tc::assert_no_null_terminator(cont); return cont; } template std::pair< tc::iterator_t>, bool > multi_index_try_emplace_with_key(boost::multi_index::detail::hashed_index& hashed_index, K const& key, ValueTypeCtorArgs&& ... valuetypectorargs) MAYTHROW { if(auto it = tc::cont_find(hashed_index, key)) { return std::make_pair(tc_move(it), false); } else { return hashed_index.emplace(tc_move_if_owned(valuetypectorargs)...); // MAYTHROW } } template std::pair< tc::iterator_t>, bool > multi_index_try_emplace_with_key(boost::multi_index::detail::ordered_index& ordered_index, K const& key, ValueTypeCtorArgs&& ... valuetypectorargs) MAYTHROW { auto it = ordered_index.lower_bound(key); if (tc::end(ordered_index)==it || ordered_index.key_comp()(key, ordered_index.key_extractor()(*it))) { return std::make_pair( tc::cont_must_emplace_before(ordered_index, tc_move(it), tc_move_if_owned(valuetypectorargs)...), // MAYTHROW true ); } else { return std::make_pair(tc_move(it), false); } } template std::pair>, bool> map_query_cache(boost::multi_index::detail::hashed_index& hashed_index, K&& key) MAYTHROW { if (auto it = tc::cont_find(hashed_index, tc::as_const(key))) { return std::make_pair(tc_move(it), false); } else { return hashed_index.emplace(tc_move_if_owned(key)); // MAYTHROW } } template void cont_must_erase(Cont& cont, Key&& key) noexcept { VERIFYEQUAL( cont.erase(tc_move_if_owned(key)), 1u ); } template bool cont_try_erase(Cont& cont, Key&& key) noexcept { switch_no_default(cont.erase(tc_move_if_owned(key))) { case 0: return false; case 1: return true; }; } template< typename Cont, typename SubRng > void cont_erase_range(Cont& cont, SubRng const& rng) noexcept { cont.erase(tc::begin(rng), tc::end(rng)); } ///////////////////////////////////////////////////// // remove_count_erase template [[nodiscard]] typename tc::size_proxy< typename boost::range_size::type > remove_count_erase_if(Cont& cont, Pred pred) noexcept { typename boost::range_size::type count=0; tc::filter_inplace( cont, [&]( auto&& t ) noexcept ->bool { bool const b=tc_invoke(pred, tc_move_if_owned(t)); count += (b ? 1 : 0); return !b; } ); return tc::size_proxy< typename boost::range_size::type >(count); } template [[nodiscard]] typename tc::size_proxy< typename boost::range_size::type > remove_count_erase(Cont& cont, T const& t) noexcept { return remove_count_erase_if( cont, [&](auto const& _) noexcept { return tc::equal_to(_, t); } ); } ///////////////////////////////////////////////////// // reverse_inplace // inplace algorithms should accept only lvalue containers, but reverse_inplace is called with // subranges of containers, so it must accept rvalues. template constexpr void reverse_inplace(Rng&& rng) noexcept { if constexpr( has_mem_fn_reverse> ) { rng.reverse(); } else { std::reverse(tc::begin(rng), tc::end(rng)); } } template [[nodiscard]] auto ordered_unique(Rng&& rng, Less less) noexcept code_return_decltype ( _ASSERTDEBUG( tc::is_sorted( rng, less ) );, tc::adjacent_unique( tc_move_if_owned(rng), std::not_fn( tc_move(less) ) ) ) template [[nodiscard]] auto ordered_unique(Rng&& rng) return_decltype_noexcept( tc::ordered_unique( tc_move_if_owned(rng), tc::fn_less() ) ) template [[nodiscard]] auto sort_inplace_unique(Rng&& rng, Less less = Less()) noexcept code_return_decltype( static_assert( !std::is_reference::value ); tc::sort_inplace( rng, less );, tc::ordered_unique( tc_move_if_owned(rng), tc_move(less) ) ) template [[nodiscard]] auto sort_unique(Rng&& rng, Less less) return_decltype_noexcept( tc::ordered_unique(tc::sort(tc_move_if_owned(rng), less), less) ) template [[nodiscard]] auto sort_unique(Rng&& rng) return_decltype_noexcept( sort_unique(tc_move_if_owned(rng), tc::fn_less()) ) template [[nodiscard]] constexpr auto constexpr_sort_unique(Rng&& rng, Less&& less=Less()) noexcept { return tc_modified( tc::make_static_vector()>(tc_move_if_owned(rng)), tc::sort_unique_inplace(_, tc_move_if_owned(less)) ); } namespace find_first_or_unique_adl { template< typename RangeReturn, IF_TC_CHECKS(bool c_bCheckUnique,) typename T, T... t, typename U > [[nodiscard]] constexpr decltype(auto) find_first_or_unique_impl(adl_tag_t, IF_TC_CHECKS(tc::constant,) std::integer_sequence, U const& u) noexcept { #ifdef _CHECKS if constexpr (c_bCheckUnique) { // This assert is stronger than the usual find_unique precondition, which allows duplicates as long as they are not searched for. static_assert( tc::is_strictly_sorted(tc::constexpr_sort(tc::make_array(tc::aggregate_tag, t...))) ); } #endif return ((tc::equal_to(t, u)) || ...); } } template< typename RangeReturn, typename SetType, typename T, typename Less > void binary_find_unique(tc::unordered_set const& rng, T const& t, Less less) noexcept = delete; namespace binary_find_first_or_unique_adl { template< typename RangeReturn, bool c_bAssertUniqueness, typename Rng, typename T, typename Pred > [[nodiscard]] decltype(auto) binary_find_first_or_unique(Rng&& rng, T const& t, Pred predLessOr3way) noexcept { // The result of tc::binary_find_unique must be unambiguous. In general, this means that rng is strictly // ordered. In some cases, it is convenient to allow multiple occurrences of the same item in // rng, which is not a problem as long as these items are not searched for. // preserve order of arguments for 3way predicates auto const c_b3way = tc::is_comparison_category; // tc_static_auto_constexpr_capture causes ICE auto it=[&]() noexcept { if constexpr(c_b3way) { _ASSERTDEBUG( tc::is_sorted(rng, tc::lessfrom3way(std::ref(predLessOr3way))) ); return tc::lower_bound( rng, t, tc::lessfrom3way(std::ref(predLessOr3way)) ); } else { _ASSERTDEBUG( tc::is_sorted(rng, predLessOr3way) ); return tc::lower_bound( rng, t, std::ref(predLessOr3way) ); } }(); if( it==tc::end( rng ) ) { return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } else { auto const Greater = [&](auto const& elem) noexcept { if constexpr(c_b3way) { return std::is_gt(predLessOr3way(elem, t)); } else { return predLessOr3way(t, elem); } }; auto && ref=*it; if (Greater(tc::as_const(ref))) { return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } else { #ifdef _CHECKS if constexpr(c_bAssertUniqueness) { auto itNext = tc_modified(it, ++_); _ASSERT( tc::end(rng)==itNext || Greater(tc::as_const(*itNext))); } #endif return RangeReturn::pack_element(it,tc_move_if_owned(rng),tc_move_if_owned(ref)); } } } } template< typename RangeReturn, typename Rng, typename T, typename Pred > [[nodiscard]] decltype(auto) binary_find_unique(Rng&& rng, T const& t, Pred predLessOr3way) noexcept { return binary_find_first_or_unique_adl::binary_find_first_or_unique(tc_move_if_owned(rng), t, tc_move(predLessOr3way)); } template< typename RangeReturn, typename Rng, typename T > [[nodiscard]] decltype(auto) binary_find_unique(Rng&& rng, T const& t) noexcept { return tc::binary_find_unique( tc_move_if_owned(rng), t, tc::fn_less() ); } template< typename RangeReturn, typename Rng, typename T, typename Pred > [[nodiscard]] decltype(auto) binary_find_first(Rng&& rng, T const& t, Pred predLessOr3way) noexcept { return binary_find_first_or_unique_adl::binary_find_first_or_unique(tc_move_if_owned(rng), t, tc_move(predLessOr3way)); } template< typename RangeReturn, typename Rng, typename T > [[nodiscard]] decltype(auto) binary_find_first(Rng&& rng, T const& t) noexcept { return tc::binary_find_first( tc_move_if_owned(rng), t, tc::fn_less() ); } // would be cleaner to search on the distance metric (starting with lower_bound(rng,0)), // but subtraction may cause unnecessary overflows template< typename Rng, typename T > [[nodiscard]] auto binary_closest(Rng&& rng, T const& t) noexcept { auto it = tc::lower_bound(rng, t); if( tc::begin(rng)==it ) { return it; } else if( tc::end(rng)==it ) { return tc_modified(it, --_); } else { auto itPrior = tc_modified(it, --_); return (t - *itPrior) < (*it - t) ? itPrior : it; } } template< typename RngA, typename RngB, typename Comp, typename FuncElementA, typename FuncElementB, typename FuncElementBoth > auto interleave_may_remove_current_iterator(RngA&& rngA, RngB&& rngB, Comp comp, FuncElementA fnElementA, FuncElementB fnElementB, FuncElementBoth fnElementBoth) noexcept -> tc::common_type_t< decltype(tc::continue_if_not_break(fnElementA, tc::begin(rngA), tc::begin(rngB))), decltype(tc::continue_if_not_break(fnElementB, tc::begin(rngA), tc::begin(rngB))), decltype(tc::continue_if_not_break(fnElementBoth, tc::begin(rngA), tc::begin(rngB))) > { auto itA=tc::begin(rngA); auto itEndA=tc::end(rngA); auto itB=tc::begin(rngB); auto itEndB=tc::end(rngB); if( itA==itEndA ) goto endA; if( itB==itEndB ) goto endB; for(;;) { if(tc_auto_cref(order, comp( tc::as_const(*itA), tc::as_const(*itB) )); std::is_lt(order)) { tc_return_if_break(tc::continue_if_not_break(fnElementA, itA++, itB)) if( itA==itEndA ) goto endA; } else if(tc::is_eq(order)) { tc_return_if_break(tc::continue_if_not_break(fnElementBoth, itA++, itB++)) if( itA==itEndA ) goto endA; if( itB==itEndB ) goto endB; } else { _ASSERTDEBUG(std::is_gt(order)); tc_return_if_break(tc::continue_if_not_break(fnElementB, itA, itB++)) if( itB==itEndB ) goto endB; } } endB: while (itA != itEndA) tc_return_if_break(tc::continue_if_not_break(fnElementA, itA++, itEndB)) tc_return_cast(tc::continue_); endA: while(itB != itEndB) tc_return_if_break(tc::continue_if_not_break(fnElementB, itEndA, itB++)) tc_return_cast(tc::continue_); } template< typename RngA, typename RngB, typename Comp, typename FuncElementA, typename FuncElementB, typename FuncElementBoth > auto interleave_may_remove_current(RngA&& rngA, RngB&& rngB, Comp comp, FuncElementA fnElementA, FuncElementB fnElementB, FuncElementBoth fnElementBoth) noexcept { return interleave_may_remove_current_iterator(rngA, rngB, comp, [&](auto const& lhs, tc::unused /*rhs*/) noexcept { return tc_invoke(fnElementA, *lhs); }, [&](tc::unused /*lhs*/, auto const& rhs) noexcept { return tc_invoke(fnElementB, *rhs); }, [&](auto const& lhs, auto const& rhs) noexcept { return tc_invoke(fnElementBoth, *lhs, *rhs); } ); } namespace interleave_2_detail { DEFINE_TAG_TYPE(lhs_tag) DEFINE_TAG_TYPE(rhs_tag) DEFINE_TAG_TYPE(lhsrhs_tag) namespace no_adl { // MSVC (from 15.8 to 17.0.2) sometimes crashes when this is inlined as a lambda in interleave_2. template struct interleave_2_sink { tc::iterator_t& m_itrhs; tc::sentinel_t const& m_endrhs; Comp const m_comp; Sink const& m_sink; template constexpr auto operator()(Lhs&& lhs) const& MAYTHROW -> tc::common_type_t< decltype(tc::continue_if_not_break(m_sink, lhs_tag, std::declval())), decltype(tc::continue_if_not_break(m_sink, rhs_tag, *m_itrhs)), decltype(tc::continue_if_not_break(m_sink, lhsrhs_tag, std::declval(), *m_itrhs)), tc::constant > { for (;;) { if( m_itrhs == m_endrhs ) { return tc::continue_if_not_break(m_sink, lhs_tag, tc_move_if_owned(lhs)); } else { decltype(auto) rhs = *m_itrhs; if(auto const order=tc_invoke(m_comp, tc::as_const(lhs), tc::as_const(rhs) ); std::is_lt(order)) { return tc::continue_if_not_break(m_sink, lhs_tag, tc_move_if_owned(lhs)); } else if (std::is_gt(order)) { tc_return_if_break(tc::continue_if_not_break(m_sink, rhs_tag, tc_move_if_owned(rhs))) ++m_itrhs; } else { tc_return_if_break(tc::continue_if_not_break(m_sink, lhsrhs_tag, tc_move_if_owned(lhs), tc_move_if_owned(rhs))) ++m_itrhs; return tc::constant(); } } } } }; template struct SDemultiplexByTagSink { // MSVC workaround: not a lambda for shorter symbol names SinkLhs const m_sinklhs; SinkRhs const m_sinkrhs; SinkLhsRhs const m_sinklhsrhs; template constexpr auto operator()(lhs_tag_t, Args&&... args) const& return_decltype_MAYTHROW( m_sinklhs(tc_move_if_owned(args)...) ) template constexpr auto operator()(rhs_tag_t, Args&&... args) const& return_decltype_MAYTHROW( m_sinkrhs(tc_move_if_owned(args)...) ) template constexpr auto operator()(lhsrhs_tag_t, Args&&... args) const& return_decltype_MAYTHROW( m_sinklhsrhs(tc_move_if_owned(args)...) ) }; template struct SExchangedRangeSink { // MSVC workaround: not a lambda for shorter symbol names Sink const m_sink; template constexpr auto operator()(lhs_tag_t, Rhs&& rhs) const& return_decltype_MAYTHROW( tc_invoke(m_sink, rhs_tag, tc_move_if_owned(rhs)) ) template constexpr auto operator()(rhs_tag_t, Lhs&& lhs) const& return_decltype_MAYTHROW( tc_invoke(m_sink, lhs_tag, tc_move_if_owned(lhs)) ) template constexpr auto operator()(lhsrhs_tag_t, Rhs&& rhs, Lhs&& lhs) const& return_decltype_MAYTHROW( tc_invoke(m_sink, lhsrhs_tag, tc_move_if_owned(lhs), tc_move_if_owned(rhs)) ) }; template struct SExchangedRangeComp { // MSVC workaround: not a lambda for shorter symbol names Comp const m_comp; constexpr auto operator()(auto const& rhs, auto const& lhs) const& MAYTHROW { return tc::negate(tc_invoke(m_comp, lhs, rhs)); } }; } template< typename RngLhs, tc::range_with_iterators RngRhs, typename Comp, typename Sink> requires tc::prefers_for_each || (!tc::prefers_for_each) constexpr auto internal_interleave_2(RngLhs&& rnglhs, RngRhs&& rngrhs, Comp&& comp, Sink const sink) MAYTHROW { auto itrhs=tc::begin(rngrhs); auto endrhs=tc::end(rngrhs); tc_return_if_break(tc::for_each( rnglhs, no_adl::interleave_2_sink, Sink>{itrhs, endrhs, tc_move_if_owned(comp), sink} )); while (itrhs != endrhs) { tc_return_if_break(tc::continue_if_not_break(sink, rhs_tag, *itrhs)) ++itrhs; } return tc::implicit_cast, Sink>>() ))>(tc::constant()); } template< tc::range_with_iterators RngLhs, tc::prefers_for_each RngRhs, typename Comp, typename Sink> requires (!tc::prefers_for_each) constexpr auto internal_interleave_2(RngLhs&& rnglhs, RngRhs&& rngrhs, Comp&& comp, Sink&& sink) MAYTHROW { return interleave_2_detail::internal_interleave_2( tc_move_if_owned(rngrhs), tc_move_if_owned(rnglhs), no_adl::SExchangedRangeComp>{tc_move_if_owned(comp)}, no_adl::SExchangedRangeSink>{tc_move_if_owned(sink)} ); } } template constexpr auto interleave_2(auto&& rnglhs, auto&& rngrhs, auto&& comp, SinkLhs&& sinklhs, SinkRhs&& sinkrhs, SinkLhsRhs&& sinklhsrhs) MAYTHROW { return interleave_2_detail::internal_interleave_2( tc_move_if_owned(rnglhs), tc_move_if_owned(rngrhs), tc_move_if_owned(comp), interleave_2_detail::no_adl::SDemultiplexByTagSink, tc::decay_t, tc::decay_t>{ tc_move_if_owned(sinklhs), tc_move_if_owned(sinkrhs), tc_move_if_owned(sinklhsrhs) } ); } namespace no_adl { template struct SInterleaveImpl { tc::decay_t m_compare; bool HasBetterElement(bool* const, tc::unused) const& noexcept { return false; } template< typename PairItItBest, typename PairItIt0, typename... Args > bool HasBetterElement(bool* const itb, PairItItBest const& argBest, PairItIt0 const& pairitit0, Args const&... args) const& noexcept { if (pairitit0.first != pairitit0.second) { if(tc_auto_cref(order, m_compare(tc::as_const(*argBest.first), tc::as_const(*pairitit0.first))); std::is_lt(order)) { *itb = false; return HasBetterElement(tc_modified(itb, ++_), argBest, args...); } else if(tc::is_eq(order)) { bool b = HasBetterElement(tc_modified(itb, ++_), argBest, args...); *itb = !b; return b; } else { _ASSERTDEBUG(std::is_gt(order)); *itb = !HasBetterElement(tc_modified(itb, ++_), pairitit0, args...); return true; } } else { *itb = false; return HasBetterElement(tc_modified(itb, ++_), argBest, args...); } } bool FindBest(bool* const) const& { return false; } template< typename PairItIt0, typename... Args > bool FindBest(bool* const itb, PairItIt0 const& pairitit0, Args const&... args) const& { if (pairitit0.first != pairitit0.second) { *itb = !HasBetterElement(tc_modified(itb, ++_), pairitit0, args...); return true; } else { *itb = false; return FindBest(tc_modified(itb, ++_), args...); } } SInterleaveImpl(Compare&& compare) noexcept : m_compare(tc_move_if_owned(compare)) {} template< typename Func, std::size_t... I, typename... PairItIt > tc::break_or_continue operator()(Func func, std::index_sequence, PairItIt... pairitit) const noexcept { bool ab[sizeof...(PairItIt)]; while (FindBest(tc::begin(ab), pairitit...)) { tc_return_if_break(tc::continue_if_not_break(func, std::make_pair(pairitit.first, tc::at(ab, I))... )); ([](auto& it, bool const b) noexcept {if (b) ++it;}(pairitit.first, tc::at(ab,I)), ...); } return tc::continue_; } }; } template< typename Compare, typename Func, typename... Rng > tc::break_or_continue interleave_n(Compare&& compare, Func&& func, Rng&&... rng) noexcept { return no_adl::SInterleaveImpl(tc_move_if_owned(compare))( tc_move_if_owned(func), std::index_sequence_for(), std::make_pair( tc::begin(rng), tc::end(rng) )... ); } template [[nodiscard]] auto common_prefix(RngRng&& rngrng) noexcept { auto&& rngFront = tc::front(rngrng); return tc::accumulate( tc::begin_next(rngrng), tc::take(tc_move_if_owned(rngFront), tc::end(rngFront)), [&](auto& rngResult, auto const& rng) noexcept { tc::take_inplace(rngResult, boost::mismatch(rngResult, rng).first); } ); } template< typename T, typename Rng > [[nodiscard]] auto make_variant_range_filter(Rng&& rng) noexcept { return tc::transform( tc::filter( tc_move_if_owned(rng), tc_fn(std::holds_alternative) ), tc_fn(tc::get) ); } template< typename RangeReturn, typename Rng, typename T> requires (!RangeReturn::requires_iterator) [[nodiscard]] constexpr decltype(auto) linear_at(Rng&& rng, T n) noexcept { return tc::find_first_if(tc_move_if_owned(rng), [&](tc::unused) noexcept { if(0==n) { return true; } else { --n; return false; } }); } // Create an infinite range by repeatedly applying funcIterate to t template auto iterate(T&& t, FuncIterate&& funcIterate) noexcept { return tc::generator_range_output const&>([funcIterate=tc::make_reference_or_value(tc_move_if_owned(funcIterate)),t_=tc::make_reference_or_value(tc_move_if_owned(t))](auto func) noexcept { auto t = *t_; tc_return_if_break(tc::continue_if_not_break(func,tc::as_const(t))) for (;;) { tc_invoke(*funcIterate, t); tc_return_if_break(tc::continue_if_not_break(func,tc::as_const(t))) } }); } template decltype(auto) concat_nonempty_with_separator(RngSep&& rngSep, Rngs&&... rngs) noexcept { return tc::join_with_separator( tc_move_if_owned(rngSep), tc::filter(tc::make_range(tc_move_if_owned(rngs)...), std::not_fn(tc_fn(tc::empty))) ); } template decltype(auto) concat_with_separator(RngSep&& rngSep, Rng0&& rng0, Rngs&&... rngs) noexcept { return tc::concat(tc_move_if_owned(rng0), tc::concat(/*copy if needed*/tc::implicit_cast(rngSep), tc_move_if_owned(rngs))...); } template void fill(Rng&& rng, Val const& value) noexcept { tc::for_each(tc_move_if_owned(rng), [&](auto&& element) noexcept { tc_move_if_owned(element) = value; }); } } ================================================ FILE: tc/algorithm/algorithm.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "../base/assert_defs.h" #include "../container/container.h" // tc::vector #include "../unittest.h" #include "../range/concat_adaptor.h" #include "../range/join_adaptor.h" #include "../range/repeat_n.h" #include "../string/spirit_algorithm.h" #include "interleave_ranges.h" #include namespace { [[maybe_unused]] void static_tests() noexcept { // explicit_cast with explicit move constructor struct SMove { explicit SMove(tc::vector&&) noexcept {} }; void(tc::explicit_cast(tc::vector{})); // explicit_cast with explicit constructor struct SCopy { explicit SCopy(tc::vector) noexcept {} }; void(tc::explicit_cast(tc::vector{})); } UNITTESTDEF( sort_accumulate_each_unique_range_2 ) { struct SValAccu final { SValAccu(int val, int accu) noexcept : m_val(val), m_accu(accu) {} int m_val; int m_accu; }; { tc::vector< SValAccu > vec; for( int i=0; i < 5; ++i ) { tc::cont_emplace_back( vec, 1, 1 ); } tc::sort_accumulate_each_unique_range( vec, [](SValAccu const& lhs, SValAccu const& rhs) noexcept { return lhs.m_val < rhs.m_val; }, [](SValAccu& lhs, SValAccu const& rhs) noexcept { lhs.m_accu+=rhs.m_accu; } ); TEST_EQUAL( 1, vec.size() ); TEST_EQUAL( 1, tc::front(vec).m_val ); TEST_EQUAL( 5, tc::front(vec).m_accu ); } } UNITTESTDEF(filter_no_self_assignment_of_rvalues) { struct S { S() {} S(S const&){} S& operator=(S&& other) { _ASSERT(&other != this); return *this; } }; tc::vector vs{5,S{}}; tc::sort_accumulate_each_unique_range( vs, [&](tc::unused, tc::unused) noexcept { return false; }, [&](auto&, tc::unused) noexcept { } ); } UNITTESTDEF( trim_leftright_if ) { tc::vector v{1,2,3,4,5,6,7,7,7}; decltype(auto) left = tc::trim_left_if(v, [] (int const n) noexcept {return n<4;}); STATICASSERTSAME(decltype(left), tc::iterator_range_t&>); TEST_RANGE_EQUAL(left, (tc::literal_range_of<4, 5, 6, 7, 7, 7>)); decltype(auto) left_then_right = tc::trim_right_if(tc_move(left), [] (int const n) noexcept {return n==7;}); STATICASSERTSAME(decltype(left_then_right), tc::iterator_range_t&>&&); TEST_RANGE_EQUAL(left_then_right, (tc::literal_range_of<4, 5, 6>)); decltype(auto) both = tc::trim_if(v, [](int const n) { return n < 4 || n == 7; }); STATICASSERTSAME(decltype(both), tc::iterator_range_t&>); TEST_RANGE_EQUAL(both, (tc::literal_range_of<4, 5, 6>)); decltype(auto) both_prvalue = tc::trim_if(tc::decay_copy(v), [](int const n) { return n < 4 || n == 7; }); STATICASSERTSAME(decltype(both_prvalue), tc::slice_t>); TEST_RANGE_EQUAL(both_prvalue, (tc::literal_range_of<4, 5, 6>)); } UNITTESTDEF( is_sorted ) { { int a[]={0}; _ASSERT( tc::is_sorted(a) ); } { int a[]={0,0}; _ASSERT( tc::is_sorted(a) ); } { int a[]={0,1}; _ASSERT( tc::is_sorted(a) ); } { int a[]={1,0}; _ASSERT( !tc::is_sorted(a) ); } { int a[]={0}; _ASSERT( tc::is_strictly_sorted(a) ); } { int a[]={0,0}; _ASSERT( !tc::is_strictly_sorted(a) ); } { int a[]={0,1}; _ASSERT( tc::is_strictly_sorted(a) ); } { int a[]={1,0}; _ASSERT( !tc::is_strictly_sorted(a) ); } } UNITTESTDEF( make_vector_on_r_vector_is_identity ) { tc::vector v{1,2,3}; auto pvecdata = tc::ptr_begin(v); auto vNew = tc::make_vector(tc_move(v)); _ASSERTEQUAL(tc::ptr_begin(vNew), pvecdata); } UNITTESTDEF(is_strictly_sorted){ int an[]={0,1,2,3,4,5}; _ASSERT(tc::is_strictly_sorted(an)); _ASSERT(!tc::is_strictly_sorted(tc::reverse(an))); } UNITTESTDEF(remove_inplace_parser) { tc::string input = "0123456789 *(tc::one - tc::lit(tc_ascii(">"))) > tc::lit(tc_ascii(">"))); _ASSERTEQUAL(input, "0123456789 const vecnA({3,4,7,9}); tc::vector vecnB({2,4,8,9,11,17}); auto const vecnBCopy = vecnB; tc::vector const vecnC({-100,1000}); tc::vector vecnResult = tc::make_vector(vecnA, vecnB, vecnC); tc::sort_inplace(vecnResult); auto itResult = tc::begin(vecnResult); _ASSERTEQUAL( tc::continue_, tc::interleave_n( tc::fn_compare(), [&](auto const&... pairitb) noexcept { auto tplpairitb = tc::tie(pairitb...); if (tc::get<0>(tplpairitb).second) { _ASSERTEQUAL(*itResult,*tc::get<0>(tplpairitb).first); ++itResult; } if (tc::get<1>(tplpairitb).second) { _ASSERTEQUAL(*itResult,*tc::get<1>(tplpairitb).first); *tc::get<1>(tplpairitb).first += 100; ++itResult; } if (tc::get<2>(tplpairitb).second) { _ASSERTEQUAL(*itResult,*tc::get<2>(tplpairitb).first); ++itResult; } }, vecnA, vecnB, vecnC ) ); _ASSERT(tc::equal( tc::transform(vecnBCopy, [](int const n) noexcept {return n+100;}), vecnB )); } UNITTESTDEF(NaryinterleaveBreak) { tc::vector const vecnA({ 3,4,7,9 }); tc::vector vecnB({ 2,4,8,9,11,17 }); auto const vecnBCopy = vecnB; tc::vector const vecnC({ -100,1000 }); tc::vector vecnResult = tc::make_vector(vecnA, vecnB, vecnC); tc::sort_inplace(vecnResult); auto itResult = tc::begin(vecnResult); _ASSERTEQUAL( tc::break_, tc::interleave_n( tc::fn_compare(), [&](auto const&... pairitb) noexcept { auto tplpairitb = tc::tie(pairitb...); if (tc::get<0>(tplpairitb).second) { _ASSERTEQUAL(*itResult, *tc::get<0>(tplpairitb).first); ++itResult; if (7 == *tc::get<0>(tplpairitb).first) return tc::break_; } if (tc::get<1>(tplpairitb).second) { _ASSERTEQUAL(*itResult, *tc::get<1>(tplpairitb).first); *tc::get<1>(tplpairitb).first += 100; ++itResult; } if (tc::get<2>(tplpairitb).second) { _ASSERTEQUAL(*itResult, *tc::get<2>(tplpairitb).first); ++itResult; } return tc::continue_; }, vecnA, vecnB, vecnC ) ); _ASSERT(tc::equal( tc::transform(vecnBCopy, [](int const n) noexcept {return n < 7 ? n + 100 : n; }), vecnB )); } UNITTESTDEF(InterleaveRanges) { tc::vector> const vecvecn({ {1,3,5,7,16,20}, {2,3,5,7,9,17}, {3,7,11,13,17}, {4,5,11,12,16}, {3,7,11,13,17}, {3,4,10,11,16} }); tc::vector> const vecvecnResult({ {1}, {2}, {3,3,3,3,3}, {4,4}, {5,5,5}, {7,7,7,7}, {9}, {10}, {11,11,11,11}, {12}, {13,13}, {16,16,16}, {17,17,17}, {20} }); _ASSERT(tc::equal( tc::make_vector(tc::join( tc::interleave_ranges( tc::transform( tc::iota(1,10), [](auto const n) noexcept { return tc::iota(n,10); } ) ) )), tc::make_vector(tc::join( tc::transform( tc::iota(1,10), [](auto const n) noexcept {return tc::repeat_n(n,tc::decay_copy(n));} ) )) )); _ASSERT(tc::equal( tc::make_vector(tc::join(tc::interleave_ranges(vecvecn))), tc::make_vector(tc::join(vecvecnResult)) )); } UNITTESTDEF(plurality_element_test) { auto const str = "abcdc"; _ASSERTEQUAL('c', tc::plurality_element(str)); auto const str2 = ""; _ASSERTEQUAL(std::nullopt, tc::plurality_element(str2)); auto const str3 = "a"; _ASSERTEQUAL(tc::begin(str3),tc::plurality_element(str3)); int an[] = {1,2,3,4,3,4,3,5,6}; _ASSERTEQUAL(std::optional(4), tc::plurality_element(tc::filter(an, [](auto const n) noexcept { return n % 2 == 0; }))); } static_assert(std::is_move_constructible const&>()))>::value); static_assert(!std::is_move_constructible>()))>::value); static_assert(std::is_move_constructible const&>()))>::value); static_assert(std::is_move_constructible>()))>::value); UNITTESTDEF(sort_test) { tc::vector vec1{6,1,2,9,5,0,3,4,7,8}; _ASSERT(tc::equal(tc::sort(vec1), tc::iota(0, 10))); auto rngnSorted=tc::sort(tc::vector{3,7,1,9,2,5,8,4,6,0}); _ASSERT(tc::equal(rngnSorted, tc::iota(0, 10))); tc::vector> vecpairnn1{{5,0}, {3,0}, {0,0}, {6,0}, {1,0}, {5,1}, {1,1}, {5,2}, {3,1}, {0,1}, {0,2}, {6,1}}; tc::vector> vecpairnn2{{0,0}, {0,1}, {0,2}, {1,0}, {1,1}, {3,0}, {3,1}, {5,0}, {5,1}, {5,2}, {6,0}, {6,1}}; auto const rngpairnnSorted=tc::stable_sort(tc_move(vecpairnn1),tc::projected(tc::fn_compare(),[](auto const& pairnn) noexcept { return pairnn.first; })); _ASSERT(tc::equal(rngpairnnSorted, vecpairnn2)); } #ifdef __clang__ // remove if std::sort is constexpr in xcode UNITTESTDEF(constexpr_sort_test) { std::mt19937 gen; // same sequence of numbers each time for reproducibility std::uniform_int_distribution<> dist(0, 63); tc_static_auto_constexpr_lambda(Test) = [](auto rngn) noexcept { auto vecn = tc::explicit_cast>(rngn); _ASSERTEQUAL(tc_modified(vecn, tc::sort_inplace(_)), tc_modified(vecn, tc::constexpr_sort_inplace_detail::constexpr_sort_inplace(tc::begin(_), tc::end(_), tc::fn_less()))); }; for( int i = 0; i < 17; ++i ) { Test(tc::begin_next([&](auto sink) noexcept { for(;;) tc_return_if_break(tc::continue_if_not_break(sink, dist(gen))); }, dist(gen))); Test(tc::iota(0, i)); Test(tc::reverse(tc::iota(0, i))); Test(tc::repeat_n(i, 0)); for( int j = 0; j < 7; ++j) { Test(tc::join(tc::repeat_n(i, tc::iota(0, j)))); } } } #endif } ================================================ FILE: tc/algorithm/any_accu.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/inplace.h" namespace tc { namespace no_adl { struct any_accu final { constexpr explicit any_accu() noexcept : m_b(false) {} constexpr explicit any_accu(bool b) noexcept : m_b(b) {} constexpr operator bool() const& noexcept { return m_b; } constexpr void operator()(bool const b) & noexcept { tc_inplace(m_b) || b; } private: bool m_b; }; } using no_adl::any_accu; } ================================================ FILE: tc/algorithm/append.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../base/construction_restrictiveness.h" #include "../base/inside_unwinding.h" #include "../container/container_traits.h" #include "../container/insert.h" #include "../container/cont_reserve.h" #include "../container/container.h" #include "../container/string.h" #include "../string/convert_enc.h" #include "../range/subrange.h" #include "../range/transform.h" #include "../range/repeat_n.h" #include "../range/concat_adaptor.h" #include namespace tc { namespace append_detail { template concept conv_enc_needed = tc::char_type && tc::range_with_iterators && tc::char_type> && !std::is_same>::value; // in general, do not use Cont::insert() or Cont(it, it) // iterators are slower than for_each in many cases (eg. filter ranges) template concept range_insertable = (!conv_enc_needed>) && has_mem_fn_reserve && tc::common_range && std::convertible_to< typename std::iterator_traits>::iterator_category, std::random_access_iterator_tag > && (!tc::prefers_for_each) && // it might be more efficient to append by ranges than by iterators tc::econstructionIMPLICIT==tc::construction_restrictiveness, std::iter_reference_t>>::value; } namespace append_no_adl { template< typename Cont, bool bReserve = has_mem_fn_reserve> struct [[nodiscard]] appender_type; template< typename Cont> struct [[nodiscard]] appender_type { using guaranteed_break_or_continue = tc::constant; constexpr explicit appender_type(Cont& cont) noexcept: m_cont(cont) {} Cont& m_cont; template constexpr void operator()(T&& t) const& noexcept(noexcept(tc::cont_emplace_back(m_cont, tc_move_if_owned(t)))) requires (!tc::char_like> || tc::safely_convertible_to>) && requires { tc::cont_emplace_back(std::declval(), tc_move_if_owned(t)); } { tc::cont_emplace_back(m_cont, tc_move_if_owned(t)); // MAYTHROW } // If appending random-access iterator range, use Cont::insert() to give insert the opportunity for optimizations. void chunk(append_detail::range_insertable auto&& rng) const& noexcept(noexcept( m_cont.insert(tc::end(m_cont), tc::begin(rng), tc::end(rng)) )) { NOBADALLOC(m_cont.insert(tc::end(m_cont), tc::begin(rng), tc::end(rng))); } void chunk(append_detail::conv_enc_needed> auto&& rng) const& return_MAYTHROW( tc::implicit_cast(tc::for_each(tc::convert_enc>(tc_move_if_owned(rng)), *this)) ) template requires std::is_same, unsigned char>::value auto write_offset(std::size_t n) const& noexcept { struct stream_pos_writer final { Cont& m_cont; decltype(tc::size_raw(m_cont)) m_pos; explicit stream_pos_writer(Cont& cont) noexcept : m_cont(cont) , m_pos(tc::size_raw(m_cont)) {} void mark() & noexcept { // The offset is counted from _after_ the written offset. boost::copy(tc::as_blob(tc::explicit_cast(tc::size_raw(m_cont)-m_pos-sizeof(std::uint32_t))), tc::begin_next(m_cont, m_pos)); m_pos+=sizeof(std::uint32_t); } void mark_null() & noexcept { boost::copy(tc::as_blob(std::numeric_limits::max()), tc::begin_next(m_cont, m_pos)); m_pos+=sizeof(std::uint32_t); } }; stream_pos_writer ow(m_cont); tc::for_each(tc::repeat_n(sizeof(std::uint32_t)*n, tc::explicit_cast(0)), *this); return ow; } }; template< typename Cont > struct [[nodiscard]] appender_type /*final*/: appender_type { using base_ = appender_type; using base_::base_; using base_::chunk; // We use int = 0 in parameter list because variation of // https://stackoverflow.com/questions/51933397/sfinae-method-completely-disables-base-classs-template-method-in-clang template< typename Rng, ENABLE_SFINAE, std::enable_if_t< !append_detail::conv_enc_needed> && tc::has_size && !append_detail::range_insertable >* = nullptr> constexpr auto chunk(Rng&& rng, int = 0) const& return_decltype_MAYTHROW( tc::cont_reserve(this->m_cont, this->m_cont.size()+tc::size(rng)), tc::implicit_cast(tc::for_each(tc_move_if_owned(rng), tc::base_cast(*this))) ) }; } using append_no_adl::appender_type; namespace appender_default { template constexpr auto appender_impl(Cont& cont) noexcept { return tc::appender_type(cont); } } DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(appender) template using appender_t = decltype(tc::appender(std::declval())); template concept appendable = tc::has_for_each>; // Disallow 0 == sizeof...(Rng), so that overload taking single argument tc::tuple is rejected template< typename RangeReturn = tc::return_void, typename Cont, tc::appendable Rng> constexpr decltype(auto) append(Cont&& cont, Rng&& rng) MAYTHROW { static_assert( !std::is_const::value, "Cannot append to const container" ); static_assert( !tc::range_with_iterators || std::is_lvalue_reference::value, "Append to rvalue intentional?" ); if constexpr( !tc::range_with_iterators || ( std::is_same::value && noexcept(tc::for_each(tc_move_if_owned(rng), tc::appender(cont))) ) ) { static_assert( std::is_same::value, "RangeReturn not supported, if appending to stream." ); tc::for_each(tc_move_if_owned(rng), tc::appender(cont)); } else if constexpr( tc::random_access_range || has_mem_fn_reserve ) { auto const nOffset = tc::size_raw(cont); try { tc::for_each(tc_move_if_owned(rng), tc::appender(cont)); if constexpr( !std::is_same::value ) { return RangeReturn::pack_border( tc::begin_next(cont, nOffset), cont ); } } catch (...) { tc::take_first_inplace(cont, nOffset); throw; } } else { // assume iterators are stable to get iterator to first inserted element auto const it = tc::back(cont); auto const FirstAppendedElement = [&]() noexcept { return it ? tc_modified(it, ++_) : tc::begin(cont); }; try { tc::for_each(tc_move_if_owned(rng), tc::appender(cont)); // MAYTHROW if constexpr( !std::is_same::value ) { return RangeReturn::pack_border(FirstAppendedElement(), cont); } } catch (...) { tc::take_inplace(cont, FirstAppendedElement()); throw; } } } template< typename RangeReturn = tc::return_void, typename Cont, tc::appendable... Rng> requires (1 < sizeof...(Rng)) constexpr decltype(auto) append(Cont&& cont, Rng&&... rng) MAYTHROW { return tc::append(tc_move_if_owned(cont), tc::concat(tc_move_if_owned(rng)...)); } namespace no_adl { template struct append_on_dtor_t final : tc::noncopyable, tc::inside_unwinding { tc::optional m_ocont; tc::reference_or_value m_rng; append_on_dtor_t(Cont& cont, Rng&& rng) noexcept : m_ocont(cont), m_rng(tc::aggregate_tag, tc_move_if_owned(rng)) {} append_on_dtor_t(append_on_dtor_t&& other) noexcept : m_ocont(tc_move(other).m_ocont), m_rng(tc_move(other).m_rng) { other.m_ocont = std::nullopt; } ASSIGN_BY_RENEW(append_on_dtor_t, append_on_dtor_t&&); ~append_on_dtor_t() MAYTHROW { if(m_ocont && !inside_stack_unwinding()) { tc::append(*m_ocont, *tc_move(m_rng)); // MAYTHROW } } }; } template Rng> constexpr decltype(auto) make_append_on_dtor(Cont& cont, Rng&& rng) MAYTHROW { return no_adl::append_on_dtor_t(cont, tc_move_if_owned(rng)); } namespace explicit_convert_to_container_detail { template using use_ctor=tc::constant< tc::derived_from, TTarget> && ( 0==sizeof...(RngN) || (!std::is_reference::value && !std::is_const::value) ) >; } namespace explicit_convert_adl { template concept appendable_container = has_mem_fn_push_back || has_emplace_back>::value; template... RngN> requires explicit_convert_to_container_detail::use_ctor::value constexpr TTarget explicit_convert_impl(adl_tag_t, std::type_identity, Rng0&& rng0, RngN&&... rngN) MAYTHROW { if constexpr(0 Rng0, tc::appendable... RngN> requires (!explicit_convert_to_container_detail::use_ctor::value) constexpr TTarget explicit_convert_impl(adl_tag_t, std::type_identity, Rng0&& rng0, RngN&&... rngN) MAYTHROW { TTarget cont; tc::append(cont, tc_move_if_owned(rng0), tc_move_if_owned(rngN)...); return cont; } } template< typename... Rng > [[nodiscard]] auto make_vector(Rng&&... rng) MAYTHROW { static_assert(0 < sizeof...(Rng)); return tc::explicit_cast>>(tc_move_if_owned(rng)...); } template< typename Char, typename... Rng > [[nodiscard]] auto make_str(Rng&&... rng) MAYTHROW { static_assert(0 < sizeof...(Rng)); return tc::explicit_cast>(tc_move_if_owned(rng)...); } template< typename... Rng > [[nodiscard]] auto make_str(Rng&&... rng) MAYTHROW { static_assert(0 < sizeof...(Rng)); return tc::make_str>(tc_move_if_owned(rng)...); } template< typename T, typename Rng > [[nodiscard]] auto make_unique_unordered_set(Rng&& rng) MAYTHROW { tc::unordered_set set; tc::cont_try_insert_range(set, tc_move_if_owned(rng)); return set; } template< typename Rng > [[nodiscard]] auto make_unique_unordered_set(Rng&& rng) MAYTHROW { return make_unique_unordered_set>(tc_move_if_owned(rng)); } template< typename T, typename Rng > [[nodiscard]] auto make_unordered_set(Rng&& rng) MAYTHROW { tc::unordered_set set; tc::cont_must_insert_range(set, tc_move_if_owned(rng)); return set; } template< typename Rng > [[nodiscard]] auto make_unordered_set(Rng&& rng) MAYTHROW { return make_unordered_set>(tc_move_if_owned(rng)); } } ================================================ FILE: tc/algorithm/append.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "../base/assert_defs.h" #include "append.h" #include "../unittest.h" #include "../string/format.h" #include "../static_vector.h" #include "../range/filter_adaptor.h" static_assert(tc::appendable&>); static_assert(!tc::appendable&>); static_assert(tc::appendable&>); static_assert(tc::appendable&>); static_assert(!tc::appendable, tc::string&>); static_assert(!tc::appendable, tc::string&>); static_assert(!tc::appendable, tc::string&>); UNITTESTDEF(nonappendable) { tc::vector vecnTest{1, 2, 3}; auto rngTest1=tc::transform(vecnTest, [](auto const n) noexcept { return n + 1; }); static_assert(!tc::appendable&>); auto rngTest2=tc::filter(vecnTest, [](auto const n) noexcept { return n%2==1; }); static_assert(!tc::appendable&>); } UNITTESTDEF(append_on_dtor) { { tc::string str; tc::append(str, "Hello "); { tc::make_append_on_dtor(str, "World!"); } _ASSERT(tc::equal(str, "Hello World!")); } { tc::string str; { tc_auto_cref(a1, tc::make_append_on_dtor(str, "World!")); tc_auto_cref(a2, tc::make_append_on_dtor(str, "Hello ")); } _ASSERT(tc::equal(str, "Hello World!")); } } ================================================ FILE: tc/algorithm/best_element.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../range/subrange.h" #include "../storage_for.h" #include "../base/change.h" #include "accumulate.h" #include "compare.h" namespace tc { template< typename RangeReturn, typename Better, typename Rng> [[nodiscard]] constexpr decltype(auto) best_element_impl(Better better, Rng&& rng) MAYTHROW { if constexpr( RangeReturn::requires_iterator ) { auto const itEnd=tc::end(rng); // MAYTHROW decltype(tc::begin(rng)) ait[2]={ tc::begin(rng) }; // MAYTHROW if(ait[0]==itEnd) { return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } else { tc::storage_for< tc::reference_or_value > aoref[2]; aoref[0].ctor( aggregate_tag, *ait[0] ); // MAYTHROW for(;;){ for( int i=0; i!=2; ++i ) { // we expect the compiler to unroll this loop // aoref[i] is constructed, aoref[1-i] is not constructed tc_scope_exit { aoref[i].dtor(); }; // also required in case of exception ait[1-i]=ait[i]; for(;;) { // aoref[i] is constructed, aoref[1-i] is not constructed ++ait[1-i]; if(ait[1-i]==itEnd) { return RangeReturn::pack_element(tc_move_always(ait[i]),tc_move_if_owned(rng),**tc_move_always(aoref[i])); } aoref[1-i].ctor( aggregate_tag, *ait[1-i] ); // MAYTHROW try { if( tc_invoke(better, tc::as_const(**aoref[1-i]), tc::as_const(**aoref[i])) ) { // MAYTHROW break; // only path where aoref[1-i] is not destroyed } } catch(...) { aoref[1-i].dtor(); throw; } aoref[1-i].dtor(); } } } } } else if (auto ovalue = tc::accumulate_with_front(tc_move_if_owned(rng), [&](auto& valueBest, auto&& value) noexcept { return tc::assign_better(better, valueBest, tc_move_if_owned(value)); })) { return RangeReturn::template pack_element(*tc_move(ovalue)); } else { return RangeReturn::template pack_no_element(); } } template< typename RangeReturn, typename Better, typename Rng, typename Projection = tc::identity> [[nodiscard]] constexpr decltype(auto) best_element(Better&& better, Rng&& rng, Projection&& projection = Projection()) MAYTHROW { return tc::best_element_impl(tc::projected(tc_move_if_owned(better), tc_move_if_owned(projection)), tc_move_if_owned(rng)); } template< typename RangeReturn, typename Rng, typename Projection = tc::identity > [[nodiscard]] constexpr decltype(auto) min_element(Rng&& rng, Projection&& projection = Projection()) MAYTHROW { return tc::best_element(tc::fn_less(), tc_move_if_owned(rng), tc_move_if_owned(projection)); } template< typename RangeReturn, typename Rng, typename Projection = tc::identity > [[nodiscard]] constexpr decltype(auto) max_element(Rng&& rng, Projection&& projection = Projection()) MAYTHROW { return tc::best_element(tc::fn_greater(), tc_move_if_owned(rng), tc_move_if_owned(projection)); } template< typename RangeReturn, typename Rng, typename T > [[nodiscard]] constexpr decltype(auto) closest_element(Rng&& rng, T const& t) MAYTHROW { return tc::min_element(tc_move_if_owned(rng), [&](auto const& elem) MAYTHROW { return std::abs(t - elem); }); } } ================================================ FILE: tc/algorithm/binary_operators.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/derivable.h" #include "../base/generic_macros.h" #include "../base/modified.h" #include "../base/empty_chain.h" #include "round.h" #ifdef MSVC_WORKAROUND #include #include #endif namespace tc { #pragma push_macro("GENERIC_OP_BODY") #define GENERIC_OP_BODY(op, operation_body) \ template< typename Lhs, typename Rhs \ > \ requires is_operation_available \ [[nodiscard]] friend constexpr auto operator op(Lhs&& lhs, Rhs&& rhs) noexcept { \ static_assert(tc::decayed>); \ using Result = std::conditional_t, Lhs>::value, Lhs&&, conversion_t>; \ Result _ = tc_move_if_owned(lhs); \ operation_body; \ if constexpr( std::is_same::value ) { \ return tc_move_if_owned(_); \ } else { \ static_assert( \ std::is_same>::value \ || !is_compound_available, Rhs&&>, \ "conversion_t provides a conversion, despite " #op "= being offered by the original type" \ ); \ return _; \ } \ } #pragma push_macro("DEFINE_GENERIC_OP") #define DEFINE_GENERIC_OP(name, op) \ namespace binary_operator_conversions { \ template< typename Lhs, typename Rhs > \ struct name##_conversion_type { \ using type = Lhs; \ }; \ template< typename Lhs, typename Rhs> \ struct internal_##name##_conversion_type : name##_conversion_type { }; \ } \ namespace generic_operator_helper { \ /*Checks if (TThis&& op##= TOther&&) is defined*/ \ TC_HAS_EXPR(compound_##name, (TThis)(TOther), std::declval() op##= std::declval()); \ /*Checks if (TThis&&.operator op##=(TOther&&)) is defined*/ \ TC_HAS_EXPR(mem_fn_compound_##name, (TThis)(TOther), std::declval().operator op##=(std::declval())); \ } \ namespace no_adl { \ template< typename Base = void > \ struct TC_EMPTY_BASES name : std::conditional_t::value, tc::empty_chain>, Base> { \ private: \ template< typename Lhs, typename Rhs > \ static constexpr bool is_compound_available = tc::generic_operator_helper::has_mem_fn_compound_ ##name; \ template< typename Lhs, typename Rhs > \ using conversion_t = typename binary_operator_conversions::internal_##name##_conversion_type, tc::decay_t>::type; \ /*If we're not forwarding an rvalue, we're calling the operator on the result of tc::decay_copy(Lhs&), which is tc::decay_t& \ If we're forwarding an rvalue, we're calling the operator on remove_reference_t&. But is_same, remove_reference_t>::value. */ \ template< typename Lhs, typename Rhs > \ static constexpr auto is_operation_available = \ tc::derived_from, name> \ && is_compound_available, Rhs>; \ public: \ GENERIC_OP_BODY( op, _.operator op##=(tc_move_if_owned(rhs)) ); \ }; \ \ template< typename Other, typename Base = void > \ struct TC_EMPTY_BASES external_ ##name : std::conditional_t::value, tc::empty_chain>, Base> { \ private: \ template< typename Lhs, typename Rhs > \ static constexpr bool is_compound_available = tc::generic_operator_helper::has_compound_ ##name; \ template< typename Lhs, typename Rhs > \ using conversion_t = tc::decay_t; \ static_assert( std::is_fundamental::value, "external_" #name " is only meant for fundamental types" ); \ STATICASSERTSAME( tc::decay_t, Other ); \ template< typename Lhs, typename Rhs > \ static constexpr auto is_operation_available = \ tc::derived_from, Other> \ && tc::derived_from, external_ ##name> \ /* Same reasoning as in the internal operator */ \ && is_compound_available, Rhs>; \ public: \ GENERIC_OP_BODY( op, { \ static_assert(!tc::generic_operator_helper::has_mem_fn_compound_ ##name); \ _ op##= tc_move_if_owned(rhs); \ } ); \ }; \ } \ using no_adl::name; \ using no_adl::external_ ##name; \ // By default, tc::addable &co. allow operations for which there exists a corresponding lhs.operator op=(rhs) member operator // For cases where the left-hand side requires a promoting conversion before the operation can take place, // specialize one of the tc::binary_operator_conversions::*_conversion_type structs // Example: if class A wants A+B to be performed by converting A to C: // 1. both A and C need to derive from tc::addable (C needs it to satisfy SFINAE sanity checks, A needs it so that ADL can find the generic operator+) // Note: A wouldn't need to derive from addable, if the generic operators were global, but that would increase the complexity of compiling any operator call // 2. C.operator+=(B) (with appropriate cvref qualifiers) must exist and be accessible // 3. there must be a specialization for tc::binary_operator_conversions::addable_conversion_type that defines the alias type=C; DEFINE_GENERIC_OP(addable, +); DEFINE_GENERIC_OP(subtractable, -); DEFINE_GENERIC_OP(multipliable, *); DEFINE_GENERIC_OP(dividable, /); DEFINE_GENERIC_OP(orable, |); DEFINE_GENERIC_OP(andable, &); DEFINE_GENERIC_OP(xorable, ^); DEFINE_GENERIC_OP(left_shiftable, <<); #pragma pop_macro("DEFINE_GENERIC_OP") #pragma pop_macro("GENERIC_OP_BODY") template< typename Base = void > using additive = addable>; template< typename Base = void > using multiplicative = multipliable>; template< typename Base = void > using arithmetic = additive>; template< typename Base = void > using setlike = andable>>>; template< typename Other, typename Base = void > using external_additive = external_addable>; template< typename Other, typename Base = void > using external_multiplicative = external_multipliable>; template< typename Other, typename Base = void > using external_arithmetic = external_additive>; // If the same conversion logic applies to multiple operations, you can specialize one of the grouped conversions defined here namespace binary_operator_conversions { #pragma push_macro("PROXY_CONVERSION") #define PROXY_CONVERSION(nameFrom, nameTo) \ template< typename Lhs, typename Rhs > \ requires requires { typename nameTo##_conversion_type::type; } \ struct internal_##nameFrom##_conversion_type \ : nameTo##_conversion_type \ {} template< typename Lhs, typename Rhs > struct additive_conversion_type { }; PROXY_CONVERSION(addable, additive); PROXY_CONVERSION(subtractable, additive); template< typename Lhs, typename Rhs > struct multiplicative_conversion_type { }; PROXY_CONVERSION(multipliable, multiplicative); PROXY_CONVERSION(dividable, multiplicative); template< typename Lhs, typename Rhs > struct arithmetic_conversion_type { }; PROXY_CONVERSION(addable, arithmetic); PROXY_CONVERSION(subtractable, arithmetic); PROXY_CONVERSION(multipliable, arithmetic); PROXY_CONVERSION(dividable, arithmetic); template< typename Lhs, typename Rhs > struct setlike_conversion_type { }; PROXY_CONVERSION(andable, setlike); PROXY_CONVERSION(orable, setlike); PROXY_CONVERSION(xorable, setlike); PROXY_CONVERSION(subtractable, setlike); #pragma pop_macro("PROXY_CONVERSION") } namespace scalar_binary_op_detail::no_adl { template struct func { Rhs const& m_rhs; template constexpr auto operator()(LhsElement&& lhselem) const& return_decltype_allow_xvalue_MAYTHROW( // should be NOEXCEPT, but NOEXCEPT does not allow xvalues. tc_invoke(FnOp(), tc_move_if_owned(lhselem), m_rhs) ) }; struct no_prepost_scalar_operation { static constexpr void pre(tc::unused /*scalar*/) noexcept {} static constexpr void post(tc::unused /*lhs*/, tc::unused /*scalar*/) noexcept {} }; template struct assert_zero_to_scalar_relation final : no_prepost_scalar_operation { template static constexpr void pre(Scalar const& scalar) noexcept { _ASSERTE( Pred()(tc::explicit_cast(0), scalar) ); } }; } using scalar_binary_op_detail::no_adl::no_prepost_scalar_operation; using assert_non_zero_scalar = scalar_binary_op_detail::no_adl::assert_zero_to_scalar_relation; using assert_non_negative_scalar = scalar_binary_op_detail::no_adl::assert_zero_to_scalar_relation; using assert_positive_scalar = scalar_binary_op_detail::no_adl::assert_zero_to_scalar_relation; #pragma push_macro("DEFINE_SCALAR_OP") #define DEFINE_SCALAR_OP(name, op, fnop, fnassignop, prepostopdefault) \ namespace no_adl { \ template \ struct TC_EMPTY_BASES scalar_ ## name : std::conditional_t::value, tc::empty_chain>, Base> { \ template Lhs, typename Rhs \ IF_MSVC_WORKAROUND_ELSE( \ TC_FWD(BOOST_PP_COMMA() typename=decltype BOOST_PP_LPAREN()), /* workaround VS17.8 compiler bug: https://developercommunity.visualstudio.com/t/template-member-function-is-not-recogniz/10504199 */ \ TC_FWD(> requires requires {) \ ) \ std::declval().template transform(std::declval>())IF_NO_MSVC_WORKAROUND(;) \ IF_MSVC_WORKAROUND_ELSE( \ BOOST_PP_RPAREN()>, \ } \ ) \ [[nodiscard]] friend constexpr decltype(auto) operator op(Lhs&& lhs, Rhs const& rhs) noexcept { \ PrePostOperation::pre(rhs); \ decltype(auto) _ = tc_move_if_owned(lhs).template transform(scalar_binary_op_detail::no_adl::func{rhs}); \ PrePostOperation::post(_, rhs); \ return _; \ } \ \ template Lhs, typename Rhs> requires requires { \ tc::for_each(std::declval(), std::declval>()); \ } \ friend constexpr Lhs& operator op ## =(Lhs& lhs, Rhs const& rhs) noexcept { \ PrePostOperation::pre(rhs); \ tc::for_each(lhs, scalar_binary_op_detail::no_adl::func{rhs}); \ PrePostOperation::post(lhs, rhs); \ return lhs; \ } \ }; \ } \ using no_adl::scalar_ ## name; DEFINE_SCALAR_OP(addable, +, tc::fn_plus, tc::fn_assign_plus, tc::no_prepost_scalar_operation) DEFINE_SCALAR_OP(subtractable, -, tc::fn_minus, tc::fn_assign_minus, tc::no_prepost_scalar_operation) DEFINE_SCALAR_OP(multipliable, *, tc::fn_mul, tc::fn_assign_mul, tc::no_prepost_scalar_operation) DEFINE_SCALAR_OP(dividable, /, tc::fn_div, tc::fn_assign_div, tc::assert_non_zero_scalar) #pragma pop_macro("DEFINE_SCALAR_OP") namespace no_adl { template using scalar_multiplicative = tc::scalar_multipliable, nTransformDepth>; } using no_adl::scalar_multiplicative; } ================================================ FILE: tc/algorithm/break_or_continue.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../base/enum.h" #include "../base/noncopyable.h" #include "../base/derivable.h" #include #include namespace tc { TC_DEFINE_ENUM(break_or_continue, BOOST_PP_EMPTY(), (break_)(continue_)) [[nodiscard]] inline constexpr tc::break_or_continue continue_if(tc::bool_context bCondition) noexcept { if( bCondition ) { return tc::continue_; } else { return tc::break_; } } #define tc_return_if_break_impl(sometimes_break_value, ...) \ { \ auto boc__ = (__VA_ARGS__); \ /* Inline constexpr bools as soon as "constexpr if" works properly in MSVC */ \ MODIFY_WARNINGS_BEGIN(((disable)(4189))) /* diable warning C4189 to workaround VS2022 17.0 compiler bug: https://developercommunity.visualstudio.com/t/unexpected-warning-c4189-with-vs170-preview-21/1489426 */ \ constexpr bool bAlwaysBreaks = std::is_same>::value; \ constexpr bool bNeverBreaks = std::is_same>::value; \ MODIFY_WARNINGS_END \ if constexpr (bAlwaysBreaks) { \ return tc::constant(); \ } \ else if constexpr (!bNeverBreaks) { \ if( tc::break_ == boc__ ) { \ return sometimes_break_value; \ } \ } \ } #define tc_return_if_break(...) \ tc_return_if_break_impl(tc::break_, __VA_ARGS__) namespace continue_if_not_break_adl { template> struct impl_t final { BreakOrContinueDefault m_boc; }; inline constexpr impl_t<> impl = {tc::constant()}; template using is_break_or_continue = boost::mp11::mp_set_contains, tc::constant>, BreakOrContinue>; template requires is_break_or_continue::value constexpr impl_t operator,(BreakOrContinue boc, impl_t<> const&) noexcept { return {boc}; } // The built-in "operator," would be fine, but this is needed to protect against other libraries that overload "operator," (e.g. boost::proto). template requires (!is_break_or_continue>::value) constexpr impl_t<> const& operator,(NotBreakOrContinue&& /*notboc*/, impl_t<> const& _) noexcept { return _; } // Built-in: //template //impl_t<> const& operator,(void, impl_t<> const& _) noexcept { // return _; //} #define tc_internal_continue_if_not_break(...) tc::decay_copy(((__VA_ARGS__), tc::continue_if_not_break_adl::impl).m_boc) } template requires std::is_void::value constexpr Void implicit_cast(tc::constant) noexcept {} ////////////////////////////////////////////////////////////////////////// //// continue_if_not_break /////////////////////////////////////////////////////////////////////////// // Func returns break_or_continue template concept sinkable = tc::invocable const&, Args...>; template concept nothrow_sinkable = tc::sinkable && tc::nothrow_invocable const&, Args...>; template constexpr auto continue_if_not_break(Sink const& sink, Args&&... args) return_decltype_MAYTHROW( tc_internal_continue_if_not_break(tc_invoke_pack(sink, tc_move_if_owned(args))) ) namespace no_adl { /////////////////////////////////////////////////// // tc::move_only_function // Supports constructing tc::move_only_function from a callable with return type other than break_or_continue_. // TODO: Implement on top of std::move_only_function once it becomes available template requires (!std::is_final_v>) && std::move_constructible> struct movable_functor_adaptor_base : tc::derivable_t> { private: using base_t = tc::derivable_t>; public: movable_functor_adaptor_base(Func&& func) noexcept requires tc::safely_constructible_from : base_t(tc_move_if_owned(func)) {} movable_functor_adaptor_base(movable_functor_adaptor_base&&) = default; // not noexcept to "inherit" exception-specifier from base class movable_functor_adaptor_base(movable_functor_adaptor_base const& mfa) noexcept : base_t(tc_move_always(tc::as_mutable(tc::base_cast(mfa)))) { // On the Mac, the std::function move ctor may actually copy our movable_functor_adaptor. // Since tc::move_only_function is noncopyable, we always move here. } }; template requires requires { typename movable_functor_adaptor_base; } struct movable_functor_adaptor final : movable_functor_adaptor_base { using movable_functor_adaptor_base::movable_functor_adaptor_base; // no return_decltype_..._MAYTHROW: type is still incomplete in function signature - tc::base_cast does not compile on clang template requires requires { std::declval&>()(std::declval()...); } decltype(auto) operator()(Args&& ... args) & noexcept(noexcept(std::declval&>()(std::declval()...))) { return tc::base_cast>(*this)(tc_move_if_owned(args)...); } // no return_decltype_..._MAYTHROW: type is still incomplete in function signature - tc::base_cast does not compile on clang template requires requires { std::declval const&>()(std::declval()...); } decltype(auto) operator()(Args&& ... args) const& noexcept(noexcept(std::declval const&>()(std::declval()...))) { return tc::base_cast>(*this)(tc_move_if_owned(args)...); } }; template requires requires { typename movable_functor_adaptor_base; } struct movable_functor_adaptor final : movable_functor_adaptor_base { using movable_functor_adaptor_base::movable_functor_adaptor_base; // no return_decltype_..._MAYTHROW: type is still incomplete in function signature - tc::base_cast does not compile on clang template requires requires { tc::continue_if_not_break(std::declval&>(), std::declval()...); } tc::break_or_continue operator()(Args&& ... args) & noexcept(noexcept( tc::continue_if_not_break(std::declval&>(), std::declval()...) )) { return tc::continue_if_not_break(tc::base_cast>(*this), tc_move_if_owned(args)...); } // no return_decltype_..._MAYTHROW: type is still incomplete in function signature - tc::base_cast does not compile on clang template requires requires { tc::continue_if_not_break(std::declval const&>(), std::declval()...); } tc::break_or_continue operator()(Args&& ... args) const& noexcept(noexcept( tc::continue_if_not_break(std::declval const&>(), std::declval()...) )) { return tc::continue_if_not_break(tc::base_cast>(*this), tc_move_if_owned(args)...); } }; template< bool bNoExcept, typename Ret, typename... Args > struct move_only_function_base: tc::noncopyable { private: std::function< Ret(Args...) > m_func; public: move_only_function_base() noexcept {} // creates an empty function move_only_function_base(std::nullptr_t) noexcept {} // creates an empty function move_only_function_base(move_only_function_base&& func) noexcept : m_func(tc_move(func).m_func) {} move_only_function_base& operator=(move_only_function_base&& func) noexcept { m_func=tc_move(func).m_func; return *this; } template< typename Func > requires (!tc::decayed_derived_from) && tc::safely_constructible_from, Func&&> && tc::safely_constructible_from> move_only_function_base(Func&& func) noexcept : m_func(movable_functor_adaptor(tc_move_if_owned(func))) { static_assert(!tc::decayed_derived_from>); // TODO: static_assert(!tc::decayed_derived_from>); // Checking the noexcept value of the function call is commented out because // 1. std::ref(func)'s operator() is not noexcept // 2. tc::unordered_set's move ctor is not noexcept // 3. boost::bind overloaded operators are not noexcept //static_assert(!bNoExcept || noexcept(std::declval&>()(std::declval()...))); } explicit operator bool() const& noexcept { tc_return_cast(m_func); } Ret operator()(Args ... args) const& noexcept(bNoExcept) { return m_func(tc_move_if_owned(args)...); } friend decltype(auto) debug_output_impl(move_only_function_base const& self) noexcept { return self.m_func.target_type().name(); } }; template< typename Signature > struct move_only_function; template< typename Ret, typename... Args > struct move_only_function< Ret(Args...) > : tc::no_adl::move_only_function_base { using base_ = typename move_only_function::move_only_function_base; using base_::base_; }; template< typename Ret, typename... Args > struct move_only_function< Ret(Args...) noexcept > : tc::no_adl::move_only_function_base { using base_ = typename move_only_function::move_only_function_base; using base_::base_; }; } // no_adl using no_adl::move_only_function; inline bool cyclic_improve_impl(int const n, int& nSkipRule) noexcept { return false; } template bool cyclic_improve_impl(int n, int& nSkipRule, F0& f0, F&... f) { if (n != nSkipRule && f0()) { nSkipRule = n; return true; } else { return cyclic_improve_impl(n+1, nSkipRule, f...); } } template< typename... F > bool cyclic_improve(F... f) noexcept { int nSkipRule=-1; while(cyclic_improve_impl(0, nSkipRule, f...)) {} return -1 != nSkipRule; } } ================================================ FILE: tc/algorithm/compare.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/return_decltype.h" #include "../base/chained.h" #include "../base/enum.h" #include "../base/functors.h" #include "../base/inplace.h" #include "../base/template_func.h" #include "../base/empty_chain.h" #include "../range/transform_adaptor.h" #include "../base/change.h" #include "find.h" #include "../interval_types.h" #if defined(__clang__) && !defined(__cpp_lib_three_way_comparison) // three way comparison operators are not implemented for library types in Xcode13/14. TODO Xcode15 #include "../optional.h" #include "../variant.h" #endif #include #include #include namespace tc { // < involving NAN always returns false, so it is not even a partial order template constexpr void assert_not_isnan(T const& t) noexcept { if constexpr( std::floating_point ) { _ASSERTDEBUG( !std::isnan(t) ); } } } namespace tc { // we always use tc::compare to perform 3 way comparison and tc::compare tc::explicit_cast std::strong_ordering/partial_ordering result to std::weak_ordering template concept is_comparison_category = std::same_as; namespace no_adl { // class T derives from tc::equal_from_three_way if T has a user defined operator<=> and implements operator== with operator<=> template< typename T > struct TC_EMPTY_BASES equal_from_three_way { template // ENABLE_SFINAE is needed because of possible clang/regression gcc bug https://godbolt.org/z/z7z3nf34f. It will be fixed in clang 16. friend constexpr bool operator==(SFINAE_TYPE(T) const& lhs, SFINAE_TYPE(T) const& rhs) noexcept(noexcept(lhs<=>rhs==0)) requires requires { {lhs<=>rhs==0} -> std::same_as; } { return lhs<=>rhs==0; } }; // Default primary comparison operators // 1. operator== requires that each base class and member has associated operator== // 2. operator<=>: // 1) with auto return type: requires that each base class and member has associated operator<=> // 2) with comparison category return type: requires that each base class and member has either associated operator<=>, or both operator== and operator<. // Our chained empty base classes are not comparable by default. If we want to define primary comparison operators as default for some class with empty base(s), // we need to wrap tc::comparable around the empty base chain to make the chain comparable. template< typename EmptyBase=void > struct TC_EMPTY_BASES comparable: std::conditional_t::value, tc::empty_chain>, EmptyBase> { static_assert(std::is_empty::value, tc::empty_chain>, EmptyBase>>::value); // tc::comparable offers primary operators only for itself, not for derived types. Therefore we cannot accidently compare 2 types derived from tc::comparable. template requires std::same_as friend constexpr bool operator==(T const&, T const&) noexcept { return true; } template requires std::same_as friend constexpr auto operator<=>(T const&, T const&) noexcept { return std::weak_ordering::equivalent; } }; } using no_adl::equal_from_three_way; using no_adl::comparable; namespace inplace_adl { template constexpr auto operator-(inplace inplaceorder) noexcept { inplaceorder.m_t = 0<=>inplaceorder.m_t; } } #ifndef __clang__ using std::is_eq; using std::is_neq; #else // Xcode14 doesn't have std::is_eq & std::is_neq while Xcode13 does. constexpr bool is_eq(std::partial_ordering order) noexcept { return order==0; } constexpr bool is_neq(std::partial_ordering order) noexcept { return order!=0; } #endif namespace explicit_convert_adl { template TTarget, std::same_as TSource> constexpr TTarget explicit_convert_impl(adl_tag_t, std::type_identity, TSource const& order) noexcept { if(std::is_lt(order)) { return std::weak_ordering::less; } else if(tc::is_eq(order)) { return std::weak_ordering::equivalent; } else if(std::is_gt(order)) { return std::weak_ordering::greater; } else { _ASSERTFALSE; // unordered is not supported return std::weak_ordering::less; } } } // clang workaround template concept has_operator_3way_compare = requires(Lhs const& lhs, Rhs const& rhs) { lhs<=>rhs; }; template constexpr std::weak_ordering compare(Lhs const& lhs, Rhs const& rhs) noexcept(noexcept(lhs<=>rhs)) requires has_operator_3way_compare && (!tc::comparable_pointers) { // We tc::explicit_cast std::partial_ordering/std::strong_ordering result to std::weak_ordering. // Comparisons that return std::partial_ordering: // 1. floating point comparison // 2. library types comparison may return std::partial_ordering if they contain floating point values. e.g. std::vector, std::pair, std::variant, std::tuple tc_return_cast(lhs<=>rhs); } // similar to Synthesized three-way comparison except with tc::less instead of operator< // 1. boost::multiprecision numbers don't support three-way comparison // 2. clang library iterators don't support three-way comparison // 3. pointers: std::compare_three_way doesn't support function pointer comparison and is not implemented in Xcode13/14 template constexpr std::weak_ordering compare(Lhs const& lhs, Rhs const& rhs) noexcept(noexcept(tc::less(lhs,rhs)) && noexcept(tc::less(rhs,lhs))) requires (!has_operator_3way_compare || tc::comparable_pointers) && requires { tc::less(lhs,rhs); tc::less(rhs,lhs); } { if(tc::less(lhs,rhs)) { return std::weak_ordering::less; } else if(tc::less(rhs, lhs)){ return std::weak_ordering::greater; } else { return std::weak_ordering::equivalent; } } tc_define_fn(compare) ////////////////////////////////////////////////////////////// // macros for implementing compare on compound types #define tc_return_if_not_equal( compare ) \ if ( auto const order_ = compare; tc::is_neq(order_) ) return order_; // auto&& triggers internal error for VS2017 // decltype((lhs)) triggers internal error for VS2019 16.8.0 preview 3.0 // Not using return_decltype_MAYTHROW because noexcept(noexcept(expr)) not needed and triggering ICE in MSVC 19.28. #define tc_internal_compare_expr(fcompare, lhs, rhs, expr) \ (fcompare)( \ ([&](auto&& _) MAYTHROW -> decltype(auto) { return tc::lvalue_or_decay(VERIFYINITIALIZED(expr)); })(VERIFYINITIALIZED(lhs)), \ ([&](auto&& _) MAYTHROW -> decltype(auto) { return tc::lvalue_or_decay(VERIFYINITIALIZED(expr)); })(VERIFYINITIALIZED(rhs)) \ ) #define tc_compare_expr( expr ) tc_return_if_not_equal( tc_internal_compare_expr(tc::compare, lhs, rhs, expr) ) #define tc_compare_expr_reverse( expr ) tc_return_if_not_equal( tc_internal_compare_expr(tc::compare, rhs, lhs, expr) ) #define tc_compare_expr_reverse_if( cond, expr ) tc_return_if_not_equal( tc::negate_if(cond, tc_internal_compare_expr(tc::compare, lhs, rhs, expr)) ) #define tc_compare_expr_times_sign( expr, sign ) tc_return_if_not_equal( tc_internal_compare_expr(tc::compare, lhs, rhs, expr)*(sign) ) #define tc_compare_expr_lex( expr ) tc_return_if_not_equal( tc_internal_compare_expr(tc::lexicographical_compare_3way, lhs, rhs, expr) ) #define tc_compare_expr_lex_reverse( expr ) tc_return_if_not_equal( tc_internal_compare_expr(tc::lexicographical_compare_3way, rhs, lhs, expr) ) #define tc_compare_expr_lex_reverse_if( cond, expr ) tc_return_if_not_equal( tc::negate_if(cond, tc_internal_compare_expr(tc::lexicographical_compare_3way, lhs, rhs, expr)) ) #define tc_compare_expr_lex_times_sign( expr, sign ) tc_return_if_not_equal( tc_internal_compare_expr(tc::lexicographical_compare_3way, lhs, rhs, expr)*(sign) ) #define tc_compare_base( basetype ) \ tc_compare_expr( tc::base_cast(_) ); #define tc_compare_mask( maskmember, maskvalue ) \ tc_compare_expr( tc::explicit_cast((_.m_ ## maskmember) & (maskmember ## maskvalue)) ) #define tc_compare_aspect_if_var( tplbb, expr ) \ if (auto const tplbb = tc_internal_compare_expr(tc_fn(tc::make_tuple), lhs, rhs, tc::explicit_cast(expr)); tplbb != tc::make_tuple(true,true)) { \ if (tplbb == tc::make_tuple(false, true)) return std::weak_ordering::less; \ else if (tplbb == tc::make_tuple(true, false)) return std::weak_ordering::greater; \ } else \ #define tc_compare_aspect_if( expr ) tc_compare_aspect_if_var(UNIQUE_IDENTIFIER, expr) #define tc_compare_aspect( maskmember, maskvalue, member ) \ tc_compare_aspect_if((_.m_ ## maskmember) & (maskmember ## maskvalue)) tc_compare_expr( _.member ) #define tc_compare_aspect_lex( maskmember, maskvalue, member ) \ tc_compare_aspect_if((_.m_ ## maskmember) & (maskmember ## maskvalue)) tc_compare_expr_lex( _.member ) #define tc_hash_aspect( maskmember, maskvalue, member ) \ if((t.m_ ## maskmember) & (maskmember ## maskvalue)) { \ tc::hash_append(h, t.member ); \ tc::hash_append(h, true); \ } else { \ tc::hash_append(h, false); \ } /////////////////////////////////////////////////////////// // compare on specific types template [[nodiscard]] constexpr auto lexicographical_compare_3way(Rng const& rng) noexcept { #if 0 // TODO Xcode14 return tc::value_or(tc::find_first_if(rng, [](auto const order) { return tc::is_neq(order); }), std::weak_ordering::equal); #else // constexpr workaround tc::range_value_t order=std::weak_ordering::equivalent; tc::for_each(rng, [&](auto const& order2) noexcept { if(tc::is_neq(order2)) { order = order2; return tc::break_; } return tc::continue_; }); return order; #endif } namespace lexicographical_compare_3way_detail { TC_DEFINE_ENUM(EPrefix, eprefix, (FORBID)(ALLOW)(EQUIVALENT)) template< EPrefix eprefix, typename Lhs, typename Rhs, typename FnCompare > constexpr auto lexicographical_compare_3way_impl( Lhs const& lhs, Rhs const& rhs, FnCompare fnCompare) noexcept -> decltype(fnCompare(*tc::begin(lhs), *tc::begin(rhs))) { auto itLhs=tc::begin( lhs ); auto const itLhsEnd=tc::end( lhs ); auto itRhs=tc::begin( rhs ); auto const itRhsEnd=tc::end( rhs ); for(;;) { if( itLhs==itLhsEnd ) { if constexpr(eprefixEQUIVALENT==eprefix) { return std::weak_ordering::equivalent; // if lhs is a prefix of rhs this is considered equivalent } else { if( itRhs==itRhsEnd ) { return std::weak_ordering::equivalent; } else { _ASSERTE(eprefixALLOW==eprefix); return std::weak_ordering::less; // lhs shorter than rhs, thus < } } } if( itRhs==itRhsEnd ) { _ASSERTE(eprefixFORBID!=eprefix); return std::weak_ordering::greater; // rhs shorter than lhs, thus > } tc_return_if_not_equal( fnCompare( *itLhs, *itRhs ) ); ++itLhs; ++itRhs; } } } template< typename Lhs, typename Rhs, typename FnCompare = tc::fn_compare > [[nodiscard]] constexpr auto lexicographical_compare_3way(Lhs const& lhs, Rhs const& rhs, FnCompare&& fnCompare = FnCompare()) noexcept { return lexicographical_compare_3way_detail::lexicographical_compare_3way_impl(lhs, rhs, tc_move_if_owned(fnCompare)); } template< typename Lhs, typename Rhs, typename FnCompare = tc::fn_compare > [[nodiscard]] constexpr auto lexicographical_compare_3way_noprefix(Lhs const& lhs, Rhs const& rhs, FnCompare&& fnCompare = FnCompare()) noexcept { return lexicographical_compare_3way_detail::lexicographical_compare_3way_impl(lhs, rhs, tc_move_if_owned(fnCompare)); } template< typename Lhs, typename Rhs, typename FnCompare = tc::fn_compare > [[nodiscard]] constexpr auto lexicographical_compare_3way_prefixequivalence(Lhs const& lhs, Rhs const& rhs, FnCompare&& fnCompare = FnCompare()) noexcept { return lexicographical_compare_3way_detail::lexicographical_compare_3way_impl(lhs, rhs, tc_move_if_owned(fnCompare)); } tc_define_fn( lexicographical_compare_3way ); tc_define_fn( lexicographical_compare_3way_noprefix ); tc_define_fn( lexicographical_compare_3way_prefixequivalence ); } #if defined(__clang__) && !defined(__cpp_lib_three_way_comparison) // three way comparison operators are not implemented for library types in Xcode13/14. TODO Xcode15 namespace std { // ADL template [[nodiscard]] auto operator<=>(std::basic_string const& lhs, std::basic_string const& rhs) noexcept { return tc::lexicographical_compare_3way(lhs, rhs); } template [[nodiscard]] auto operator<=>(std::basic_string const& lhs, Char const* rhs) noexcept { return tc::lexicographical_compare_3way(lhs, rhs); } template< typename First, typename Second > [[nodiscard]] constexpr auto operator<=>(std::pair const& lhs, std::pair const& rhs) noexcept requires requires { tc::compare(lhs.first, rhs.first); tc::compare(lhs.second, rhs.second); } { tc_compare_expr( _.first ); tc_compare_expr( _.second ); return std::weak_ordering::equivalent; } template [[nodiscard]] auto operator<=>(std::vector const& lhs, std::vector const& rhs) noexcept { return tc::lexicographical_compare_3way(lhs, rhs); } template [[nodiscard]] auto operator<=>(std::array const& lhs, std::array const& rhs) noexcept { return tc::lexicographical_compare_3way(lhs, rhs); } template [[nodiscard]] constexpr auto operator<=>(std::variant const& lhs, std::variant const& rhs) noexcept requires requires { typename boost::mp11::mp_list(), std::declval()))...>; } { if(lhs.valueless_by_exception() && rhs.valueless_by_exception()) { return std::weak_ordering::equivalent; } else if(lhs.valueless_by_exception()) { return std::weak_ordering::less; } else if(rhs.valueless_by_exception()) { return std::weak_ordering::greater; } else { tc_compare_expr(_.index()); return tc::fn_visit( [](TVal const& valLhs, TVal const& valRhs) noexcept { return tc::compare(valLhs, valRhs); }, tc::never_called() )(lhs, rhs); } } template constexpr auto operator<=>(std::optional const& lhs, std::optional const& rhs ) noexcept -> decltype(tc::compare(*lhs, *rhs)) { return lhs && rhs ? tc::compare(*lhs, *rhs) : tc::compare(static_cast(lhs), static_cast(rhs)); } template constexpr std::weak_ordering operator<=>(std::optional const& opt, std::nullopt_t) noexcept { return tc::compare(static_cast(opt), false); } template>* = nullptr> constexpr auto operator<=>(std::optional const& opt, U const& value) noexcept -> decltype(tc::compare(*opt, value)) { return opt ? tc::compare(*opt, value) : std::weak_ordering::less; } } #endif namespace tc { /////////////////////////////////// // argument-wise transformation namespace no_adl { // cannot be implemented as a lambda because lambdas are not assignable template< typename Func, typename Transform> struct [[nodiscard]] projected_impl /*not final, containers may derive from predicates to benefit from EBCO*/ { tc::verify_functor_t> m_func; tc::verify_functor_t> m_transform; template constexpr auto operator()(Args&&... args) const& return_decltype_allow_xvalue_slow_MAYTHROW( tc_invoke_pack(m_func, tc_invoke(m_transform, tc_move_if_owned(args))) ) using is_transparent = void; }; } template< typename Func, typename Transform > constexpr decltype(auto) projected(Func&& func, Transform&& transform) noexcept { if constexpr( std::is_same, tc::identity>::value ) { return tc_move_if_owned(func); } else { return no_adl::projected_impl{ tc_move_if_owned(func), tc_move_if_owned(transform) }; } } /////////////////////////////////// // converse_relation template auto reverse_binary_rel(Pred&& pred) noexcept { return [pred=tc::decay_copy(tc_move_if_owned(pred))](auto const& lhs, auto const& rhs) noexcept { return pred(rhs, lhs); }; } //////////////////////////////// // Provide adapter from 3-way compare to 2-way compare template< typename FCompare> constexpr auto lessfrom3way( FCompare&& fnCompare ) noexcept { return tc::chained(tc_fn(std::is_lt), tc_move_if_owned(fnCompare)); } template< typename FCompare> constexpr auto greaterfrom3way( FCompare&& fnCompare ) noexcept { return tc::chained(tc_fn(std::is_gt), tc_move_if_owned(fnCompare)); } template< typename FCompare> constexpr auto equalfrom3way( FCompare&& fnCompare ) noexcept { return tc::chained(tc_fn(tc::is_eq), tc_move_if_owned(fnCompare)); } namespace tuple_adl { template requires requires { typename boost::mp11::mp_list(), std::declval()))...>; } constexpr auto operator<=>(tc::tuple const& lhs, tc::tuple const& rhs) noexcept { STATICASSERTEQUAL(sizeof...(T), sizeof...(U)); return tc::lexicographical_compare_3way( tc::transform(std::index_sequence_for(), [&](auto const nconstIndex) noexcept -> std::weak_ordering { return tc::compare(tc::get(lhs), tc::get(rhs)); }) ); } } } // namespace tc #define EQUAL_MEMBER_IMPL(member) (lhs.member == rhs.member) #define EQUAL_MEMBERS(...) (TC_PP_DELIMIT_TRANSFORMED_SEQ(EQUAL_MEMBER_IMPL, &&, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))) ================================================ FILE: tc/algorithm/element.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "find.h" #include "../range/range_return.h" namespace tc { template [[nodiscard]] constexpr decltype(auto) front(auto&& rng) noexcept { return tc::find_first_if(tc_move_if_owned(rng), tc::constexpr_function()); } template [[nodiscard]] constexpr decltype(auto) back(auto&& rng) noexcept { static_assert( !std::is_same::value ); static_assert( !std::is_same::value, "Use tc::empty instead of tc::back" ); static_assert( tc::bidirectional_range> ); // TODO reverse generator would also be fine, but find_last_if is not specialized for this case yet. return tc::find_last_if(tc_move_if_owned(rng), tc::constexpr_function()); } template [[nodiscard]] decltype(auto) linear_back(auto&& rng) noexcept { static_assert( !std::is_same::value ); static_assert( !std::is_same::value, "Use tc::empty instead of tc::linear_back" ); static_assert( !tc::bidirectional_range>, "Use tc::back for bidirectional ranges" ); auto it = tc::begin(rng); tc_auto_cref(itEnd, tc::end(rng)); if (it!=itEnd) { auto itNext = it; while (itEnd!=++itNext) { it = itNext; } return RangeReturn::pack_element(tc_move(it), tc_move_if_owned(rng)); } else { return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } } template [[nodiscard]] constexpr auto at(auto&& rng, typename boost::range_size< std::remove_reference_t >::type n) noexcept { static_assert( !std::is_same::value ); static_assert( !std::is_same::value, "Use tc::size instead of tc::at" ); if (n [[nodiscard]] constexpr auto linear_at(auto&& rng, typename boost::range_size< std::remove_reference_t >::type n) noexcept { static_assert( !std::is_same::value ); static_assert( !std::is_same::value, "Use tc::size_bounded instead of tc::linear_at" ); if constexpr (std::convertible_to::type, boost::iterators::random_access_traversal_tag>) { return tc::at(tc_move_if_owned(rng), tc_move(n)); } else { _ASSERTE(0<=n); tc_auto_cref(itEnd, tc::end(rng)); for (auto it=tc::begin(rng); it!=itEnd; ++it, --n) { if (0==n) { return RangeReturn::pack_element(tc_move(it), tc_move_if_owned(rng)); } } return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } } template [[nodiscard]] constexpr auto reverse_at(auto&& rng, typename boost::range_size< std::remove_reference_t >::type n) noexcept { if (n tc::element_return_type_t only_not_X(Rng&& rng, auto func) MAYTHROW { if constexpr(RangeReturn::requires_iterator) { auto const itEnd = tc::end(rng); auto const itBegin = tc::begin(rng); if(itEnd != itBegin && (itEnd == tc_modified(itBegin, ++_) || tc::explicit_cast(func()))) { return RangeReturn::pack_element(itBegin, tc_move_if_owned(rng)); } else { return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } } else { std::optional> ot; auto const boc = tc::for_each(tc_move_if_owned(rng), [&](auto&& t) MAYTHROW { if(!ot) { tc::optional_emplace(ot, RangeReturn::template pack_element(tc_move_if_owned(t))); // MAYTHROW return tc::continue_; } else { return tc::break_; } }); if(ot && (tc::continue_ == boc || tc::explicit_cast(func()))) { return *tc_move(ot); } else { return RangeReturn::template pack_no_element(); } } } } template [[nodiscard]] constexpr decltype(auto) only(auto&& rng) MAYTHROW { static_assert(std::is_same::value || std::is_same::value, "decide between tc::only_not_0<...> and tc::only_not_N<...>"); // Exclude tc::return_XXX_or_YYY. TODO: make static_assert more generic return tc::find_unique_if(tc_move_if_owned(rng), tc::constexpr_function()); } template [[nodiscard]] decltype(auto) only_not_0(auto&& rng) MAYTHROW { static_assert(!std::is_same::value && !std::is_same::value, "use tc::only<...> instead"); // TODO: make static_assert more generic return only_detail::only_not_X(tc_move_if_owned(rng), tc::never_called>()); } template [[nodiscard]] decltype(auto) only_not_N(auto&& rng) MAYTHROW { static_assert(!std::is_same::value && !std::is_same::value, "use tc::only<...> instead"); // TODO: make static_assert more generic return only_detail::only_not_X(tc_move_if_owned(rng), tc::constexpr_function()); } namespace no_adl{ template struct element_stash_impl final { static_assert(tc::decayed); [[nodiscard]] static constexpr decltype(auto) extract(Element&& elem) noexcept(noexcept(*tc_move(elem))) { #ifdef _DEBUG if constexpr(std::is_reference::value) { _ASSERTE(std::addressof(*tc::decay_copy(elem))==std::addressof(*elem)); // specialize is_stashing_element } #endif return *tc_move(elem); } }; #ifdef _MSC_VER // If Element is of type T(*)[N], MSVC erroneously deduces return type T(*)[N] for element_stash_impl::extract, // and then raises a compiler error that it cannot convert from T[N] to T(*)[N] in the return statement. // Clang correctly deduces return type T(&)[N]. Add specialization for array types to workaround this problem. template struct element_stash_impl final { using array_type=T[N]; [[nodiscard]] static constexpr array_type& extract(T(*a)[N]) noexcept { return *a; } }; #endif // MSVC 19.31.31104 claims tc::is_stashing_element::value is not of type bool when directly used in a requires clause on element_stash_impl. // Outline to template variable to avoid silent bad codegen due to wrong sfinae. template inline bool constexpr is_stashing_element_v = tc::is_stashing_element::value; // Lifetime of reference is bound to the iterator -> must keep iterator alive template requires is_stashing_element_v struct element_stash_impl final : tc::storage_for_dtor_at_end_of_scope { static_assert(tc::decayed); [[nodiscard]] constexpr decltype(auto) extract(Element&& elem) & noexcept(noexcept(*tc_move_always(this->operator*()))) { // note that constexpr only works for trivially constructible/assignable/destructible type this->ctor(tc_move(elem)); return *tc_move_always(this->operator*()); } }; } template using element_stash = no_adl::element_stash_impl>; #pragma push_macro("RETURN_REFERENCE_FROM_ELEMENT") // Make first template argument non-type to avoid name(rng) instantiating name and then name which leads to hard error. #define RETURN_REFERENCE_FROM_ELEMENT(name) \ template \ [[nodiscard]] constexpr decltype(auto) name( \ Rng&& rng, \ tc::element_stash&& stash={} \ ) return_MAYTHROW( \ stash.extract(tc::name(tc_move_if_owned(rng))) \ ) \ \ /* Use tc::fn_front() instead of tc_fn(tc::front) because the latter returns a dangling xvalue reference when used with counting ranges. */ \ /* Support tc::fn_front() for consistency*/ \ namespace no_adl { \ template \ struct [[nodiscard]] fn_ ## name final { \ template requires tc::borrowed_range \ auto operator()(Rng&& rng) const& return_decltype_allow_xvalue_MAYTHROW( \ tc::name(tc_unwrap_temporary(tc_move_if_owned(rng))) \ ) \ template \ auto operator()(Rng&& rng) const& return_decltype_allow_xvalue_MAYTHROW( \ tc_rewrap_temporary(Rng, tc::name(tc_unwrap_temporary(tc_move_if_owned(rng)))) \ ) \ }; \ template<> \ struct [[nodiscard]] fn_ ## name final { \ template requires tc::borrowed_range || tc::is_stashing_element>::value \ auto operator()(Rng&& rng, tc::element_stash&& stash={}) const& return_decltype_allow_xvalue_MAYTHROW( \ tc::name(tc_unwrap_temporary(tc_move_if_owned(rng)), tc_move(stash)) \ ) \ template \ auto operator()(Rng&& rng, tc::element_stash&& stash={}) const& return_decltype_allow_xvalue_MAYTHROW( \ tc_rewrap_temporary(Rng, tc::name(tc_unwrap_temporary(tc_move_if_owned(rng)), tc_move(stash))) \ ) \ }; \ } \ using no_adl::fn_ ## name; RETURN_REFERENCE_FROM_ELEMENT(front) RETURN_REFERENCE_FROM_ELEMENT(back) RETURN_REFERENCE_FROM_ELEMENT(linear_back) RETURN_REFERENCE_FROM_ELEMENT(only) #pragma pop_macro("RETURN_REFERENCE_FROM_ELEMENT") #pragma push_macro("RETURN_REFERENCE_FROM_INDEXED_ELEMENT") #define RETURN_REFERENCE_FROM_INDEXED_ELEMENT(fn) \ template \ [[nodiscard]] constexpr decltype(auto) fn( \ Rng&& rng, \ typename boost::range_size< std::remove_reference_t >::type i, \ tc::element_stash&& stash={} \ ) return_MAYTHROW( \ stash.extract(tc::fn(tc_move_if_owned(rng), tc_move(i))) \ ) RETURN_REFERENCE_FROM_INDEXED_ELEMENT(at) RETURN_REFERENCE_FROM_INDEXED_ELEMENT(linear_at) RETURN_REFERENCE_FROM_INDEXED_ELEMENT(reverse_at) #pragma pop_macro("RETURN_REFERENCE_FROM_INDEXED_ELEMENT") // Write as macros to keep temporary iterators alive. // By standard, the lifetime of a reference is limited to the lifetime of the iterator. #define tc_front_nodebug(rng) (*tc::begin(rng)) #define tc_at_nodebug(rng, i) *(tc::begin(rng) + static_cast(i)) template bool if_nonempty_with_only(Rng&& rng, Func func) MAYTHROW { bool bCalled=false; tc::for_each(tc_move_if_owned(rng), [&](auto&&... args) MAYTHROW { VERIFY(tc::change(bCalled, true)); // only a single element is allowed RETURNS_VOID( func(tc_move_if_owned(args)...) ); // MAYTHROW return tc::constant(); }); return bCalled; } template bool if_nonempty_with_front(Rng&& rng, Func func) MAYTHROW { return tc::break_==tc::for_each(tc_move_if_owned(rng), [&](auto&&... args) MAYTHROW{ RETURNS_VOID(func(tc_move_if_owned(args)...)); // MAYTHROW return tc::constant(); }); } template [[nodiscard]] std::pair, T> front_and_size_bounded(Rng&& rng, T nMax) MAYTHROW { static_assert(!RangeReturn::requires_iterator, "implement iterator version if needed"); tc::storage_for> ot; T n=0; tc::for_each(tc_move_if_owned(rng), [&](auto&& t) MAYTHROW { if (0==n) { ot.ctor(RangeReturn::template pack_element(tc_move_if_owned(t))); } ++n; return (nMax<=n) ? tc::break_ : tc::continue_; }); if (0==n) { tc_return_cast(RangeReturn::template pack_no_element(), tc_move(n)); } else { tc_scope_exit { ot.dtor(); }; tc_return_cast(*tc_move(ot), tc_move(n)); } } } ================================================ FILE: tc/algorithm/element.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "element.h" #include "../unittest.h" #include "../array.h" namespace { UNITTESTDEF( front ) { _ASSERTEQUAL(tc::front(tc::single(1)), 1); _ASSERTEQUAL(*tc::front(tc::single(1)), 1); _ASSERTEQUAL(tc::front(tc::make_empty_range()), std::nullopt); } } ================================================ FILE: tc/algorithm/empty.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../base/trivial_functors.h" #include "for_each.h" namespace tc { namespace empty_internal { // TODO: inline when clang supports lambdas in unevaluated contexts template constexpr auto empty(Rng&& rng) noexcept { if constexpr(has_constexpr_size) { return [&]() return_decltype_noexcept(0==constexpr_size()); } else if constexpr(has_mem_fn_empty) { return [&]() return_MAYTHROW(tc_move_if_owned(rng).empty()); } else if constexpr(tc::range_with_iterators) { return [&]() return_MAYTHROW(tc::at_end_index(rng, tc::begin_index(rng))); } else if constexpr(has_size) { return [&]() return_MAYTHROW(0==tc::size(tc_move_if_owned(rng))); // tc::size on files may throw } else { return [&]() return_MAYTHROW(tc::continue_==tc::for_each(tc_move_if_owned(rng), tc::constexpr_function{}>())); } } } template [[nodiscard]] constexpr bool empty(Rng&& rng) return_MAYTHROW( empty_internal::empty(tc_move_if_owned(rng))() ) } ================================================ FILE: tc/algorithm/empty.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "../base/assert_defs.h" #include "../unittest.h" #include "../algorithm/empty.h" #include "../container/insert.h" namespace { struct empty_generator { template tc::break_or_continue operator()(Func) const { return tc::continue_; } }; struct non_empty_generator { template tc::break_or_continue operator()(Func func) const { return tc::continue_if_not_break(func, 1); } }; UNITTESTDEF( empty_range ) { { // test container with empty() method tc::vector vec; static_assert( has_mem_fn_empty ); _ASSERT( tc::empty(vec) ); _ASSERT( tc::empty(vec) ); tc::cont_emplace_back(vec, 1); _ASSERT( !tc::empty(vec) ); _ASSERT( !tc::empty(vec) ); } { // test iterator range static_assert( !has_mem_fn_empty > ); static_assert( tc::range_with_iterators ); _ASSERT( tc::empty("") ); static_assert( !has_mem_fn_empty > ); static_assert( tc::range_with_iterators ); _ASSERT( !tc::empty("x") ); } { // test generator range _ASSERT( tc::empty( empty_generator() ) ); _ASSERT( !tc::empty( non_empty_generator() ) ); } } } ================================================ FILE: tc/algorithm/equal.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../base/noncopyable.h" #include "../base/as_lvalue.h" #include "../base/modified.h" #include "for_each.h" #include "../base/change.h" #include #include #include #include namespace tc{ namespace no_adl { template< typename Lhs, typename Rhs> struct has_parse_match final : tc::constant {}; template< typename Lhs, typename Rhs > requires requires (Lhs const& lhs, Rhs const& rhs) { lhs.parse_match(rhs); } struct has_parse_match final : tc::constant {}; } template::value && !no_adl::has_parse_match::value>* =nullptr> [[nodiscard]] constexpr auto equal_to_or_parse_match(Lhs const& lhs, Rhs const& rhs) return_decltype_MAYTHROW( tc::equal_to(lhs,rhs) ) template::value && !no_adl::has_parse_match::value>* =nullptr> [[nodiscard]] constexpr auto equal_to_or_parse_match(Lhs const& lhs, Rhs const& rhs) return_decltype_MAYTHROW( tc::implicit_cast(lhs.parse_match(rhs)) ) template::value && no_adl::has_parse_match::value>* =nullptr> [[nodiscard]] constexpr auto equal_to_or_parse_match(Lhs const& lhs, Rhs const& rhs) return_decltype_MAYTHROW( tc::implicit_cast(rhs.parse_match(lhs)) ) tc_define_fn( equal_to_or_parse_match ); //------------------------------------------------------------------------------------------------------------------------- // equal - check whether two ranges are equal - overloaded for combinations of generator and iterator based ranges namespace equal_impl { namespace no_adl { template struct reverse_pred { Pred& m_pred; constexpr reverse_pred(Pred& pred) noexcept : m_pred(pred) {} template constexpr auto operator()(Arg1&& arg1, Arg2&& arg2) const& noexcept { return m_pred(tc_move_if_owned(arg2), tc_move_if_owned(arg1)); } }; template struct is_equal_elem /*final*/ { constexpr explicit is_equal_elem( It& it, ItEnd itEnd, Pred& pred ) noexcept : m_it(it) , m_itEnd(tc_move(itEnd)) , m_pred(pred) {} template requires requires(Pred& pred, It& it, Elem const& elem) { tc_invoke(pred, tc::as_const(*it), elem); } constexpr break_or_continue operator()(Elem const& elem) const& noexcept { if (m_it == m_itEnd || !tc::explicit_cast(tc_invoke(m_pred, tc::as_const(*m_it), elem))) { return tc::break_; } ++m_it; return tc::continue_; } private: It& m_it; ItEnd m_itEnd; Pred& m_pred; }; // TODO: this does not protect us against inputs such as transform(unordered_set) template struct is_unordered_range : tc::constant {}; template struct is_unordered_range> : tc::constant {}; template struct is_unordered_range> : tc::constant {}; } template [[nodiscard]] constexpr bool starts_with(It& it, ItEnd itEnd, RRng&& rrng, Pred pred) noexcept(noexcept(tc::continue_ == tc::for_each(tc_move_if_owned(rrng), no_adl::is_equal_elem(it, tc_move(itEnd), pred)))) { static_assert(!no_adl::is_unordered_range>::value); return tc::continue_ == tc::for_each(tc_move_if_owned(rrng), no_adl::is_equal_elem(it, tc_move(itEnd), pred)); // MAYTHROW } } template [[nodiscard]] constexpr decltype(auto) starts_with(LRng&& lrng, RRng const& rrng, Pred&& pred) noexcept { static_assert(!equal_impl::no_adl::is_unordered_range>::value); auto itlrng = tc::begin(lrng); return equal_impl::starts_with(itlrng, tc::end(lrng), rrng, tc_move_if_owned(pred)) ? RangeReturn::pack_border(itlrng, tc_move_if_owned(lrng)) : RangeReturn::pack_no_border(tc_move_if_owned(lrng)); } template [[nodiscard]] constexpr decltype(auto) starts_with(LRng&& lrng, RRng const& rrng) noexcept { return starts_with(tc_move_if_owned(lrng), rrng, tc::fn_equal_to_or_parse_match()); } template requires (tc::prefers_for_each> || (!tc::prefers_for_each)) && requires(LRng const& lrng, RRng&& rrng){ equal_impl::starts_with(tc::as_lvalue(tc::begin(lrng)), tc::as_const(tc::as_lvalue(tc::end(lrng))), tc_move_if_owned(rrng), std::declval()); } [[nodiscard]] constexpr bool equal(LRng const& lrng, RRng&& rrng, Pred&& pred) MAYTHROW { static_assert(!equal_impl::no_adl::is_unordered_range>::value); constexpr bool bHasSize=tc::has_size && tc::has_size; if constexpr(bHasSize) { if(tc::size(lrng)!=tc::size(rrng)) return false; } auto it = tc::begin(lrng); tc_auto_cref(itEnd, tc::end(lrng)); return equal_impl::starts_with(it,itEnd,tc_move_if_owned(rrng),tc_move_if_owned(pred)) && (bHasSize || itEnd==it); // MAYTHROW } // forward to the symmetric case above template requires tc::prefers_for_each> && (!tc::prefers_for_each) [[nodiscard]] constexpr bool equal(LRng&& lrng, RRng const& rrng, Pred pred) noexcept { return tc::equal(rrng, tc_move_if_owned(lrng), equal_impl::no_adl::reverse_pred(pred)); } // is_arithmetic helpful for generic programming // only do if semantics are clear-cut template requires std::is_arithmetic< T >::value [[nodiscard]] constexpr bool equal(T const& lhs, T const& rhs, Pred pred) noexcept { return pred(lhs,rhs); } template [[nodiscard]] constexpr bool equal(std::pair const& lhs, std::pair const& rhs, Pred pred) noexcept { return tc::equal(lhs.first, rhs.first, pred) && tc::equal(lhs.second, rhs.second, pred); } // forward the non predicate version template [[nodiscard]] constexpr bool equal(LRng&& lrng, RRng&& rrng) return_MAYTHROW( tc::equal(tc_move_if_owned(lrng), tc_move_if_owned(rrng), tc::fn_equal_to_or_parse_match()) ) DEFINE_FN2(tc::equal, fn_equal) // no tc_define_fn to avoid ADL // boost::ends_with does not work with boost::range_iterator::type returning by value because it has input_iterator category template [[nodiscard]] constexpr decltype(auto) ends_with(LRng&& lrng, RRng const& rrng, Pred pred=Pred()) noexcept { auto itL=tc::end(lrng); auto itR=tc::end(rrng); auto const itBeginL=tc::begin(lrng); auto const itBeginR=tc::begin(rrng); for(;;) { if( itR==itBeginR ) return RangeReturn::pack_border(itL, tc_move_if_owned(lrng)); if( itL==itBeginL ) return RangeReturn::pack_no_border(tc_move_if_owned(lrng)); --itR; --itL; if( !tc::explicit_cast(pred(tc::as_const(*itL),tc::as_const(*itR))) ) return RangeReturn::pack_no_border(tc_move_if_owned(lrng)); } } namespace value_equal_to_detail { template concept safe_value = !tc::range_with_iterators || !tc::range_with_iterators::arguments>>; } template [[nodiscard]] constexpr bool value_equal_to(Lhs const& lhs, Rhs const& rhs) noexcept { if constexpr( tc::range_with_iterators && tc::range_with_iterators ) { STATICASSERTEQUAL( TC_FWD(tc::instance), TC_FWD(tc::instance) ); return tc::equal(lhs, rhs, tc::equal_to); } else { static_assert( value_equal_to_detail::safe_value ); static_assert( value_equal_to_detail::safe_value ); return tc::equal_to(lhs, rhs); } } } ================================================ FILE: tc/algorithm/equal.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "../base/assert_defs.h" #include "../unittest.h" namespace { //---- Equal with vector ------------------------------------------------------------------------------------------------- UNITTESTDEF( equal_vec_int ) { tc::vector ve; tc::vector ve_1; TEST_init_hack(tc::vector, int, v123, {1,2,3}); TEST_init_hack(tc::vector, int, v123_1, {1,2,3}); TEST_init_hack(tc::vector, int, v143, {1,4,3}); TEST_init_hack(tc::vector, int, v1234, {1,2,3,4}); TEST_RANGE_EQUAL(ve, ve); TEST_RANGE_EQUAL(ve, ve_1); TEST_RANGE_NOT_EQUAL(ve, v123); TEST_RANGE_NOT_EQUAL(v123, ve); TEST_RANGE_EQUAL(v123, v123); TEST_RANGE_EQUAL(v123, v123_1); TEST_RANGE_NOT_EQUAL(v123, v143); TEST_RANGE_NOT_EQUAL(v123, v1234); TEST_RANGE_NOT_EQUAL(v1234, v123); } UNITTESTDEF( equal_vec_int_pred ) { TEST_init_hack(tc::vector, int, v123, {1,2,3}); TEST_init_hack(tc::vector, int, v234, {2,3,4}); tc_static_auto_constexpr_lambda(ofByOne) = [](int const lhs, int const rhs) noexcept { return (lhs + 1) == rhs; }; // unsymmetrical to uncover wrong order of argument application to the predicate _ASSERT(tc::equal(v123, v234, ofByOne)); _ASSERT(!tc::equal(v234, v123, ofByOne)); _ASSERT(!tc::equal(v123, v123, ofByOne)); } //---- Equal with generators -------------------------------------------------------------------------------------------------- UNITTESTDEF( equal_generator ) { tc::vector ve; tc::vector ve_1; TEST_init_hack(tc::vector, int, v123, {1,2,3}); TEST_init_hack(tc::vector, int, v123_1, {1,2,3}); TEST_init_hack(tc::vector, int, v143, {1,4,3}); TEST_init_hack(tc::vector, int, v1234, {1,2,3,4}); auto ge = tc::make_generator_range(ve); auto g123 = tc::make_generator_range(v123); auto g143 = tc::make_generator_range(v143); auto g1234 = tc::make_generator_range(v1234); STATIC_ASSERT(tc::range_with_iterators); STATIC_ASSERT(!tc::range_with_iterators); TEST_RANGE_EQUAL(ve, ge); TEST_RANGE_EQUAL(ge, ve); //TEST_RANGE_EQUAL(ge, ge); // Fails to compile with proper msg, as it should be. TEST_RANGE_EQUAL(v123, g123); TEST_RANGE_EQUAL(g123, v123); TEST_RANGE_EQUAL(v1234, g1234); TEST_RANGE_EQUAL(g1234, v1234); TEST_RANGE_NOT_EQUAL(v123, g143); TEST_RANGE_NOT_EQUAL(g123, v143); TEST_RANGE_NOT_EQUAL(v123, g1234); TEST_RANGE_NOT_EQUAL(g123, v1234); TEST_RANGE_NOT_EQUAL(v1234, g123); TEST_RANGE_NOT_EQUAL(g1234, v123); } UNITTESTDEF( equal_generator_pred ) { TEST_init_hack(tc::vector, int, v123, {1,2,3}); TEST_init_hack(tc::vector, int, v234, {2,3,4}); auto g123 = tc::make_generator_range(v123); auto g234 = tc::make_generator_range(v234); tc_static_auto_constexpr_lambda(ofByOne) = [](int const lhs, int const rhs) noexcept { return (lhs + 1) == rhs; }; // unsymmetrical to uncover wrong order of argument application to the predicate //_ASSERT(tc::equal(g123, g234, ofByOne)); // Fails to compile with proper msg, as it should be. _ASSERT(tc::equal(v123, g234, ofByOne)); _ASSERT(!tc::equal(v234, g123, ofByOne)); _ASSERT(!tc::equal(v123, g123, ofByOne)); _ASSERT(tc::equal(g123, v234, ofByOne)); _ASSERT(!tc::equal(g234, v123, ofByOne)); _ASSERT(!tc::equal(g123, v123, ofByOne)); } UNITTESTDEF( variadic_assign_better ) { int nVar = 5; bool b=tc::assign_better(tc::fn_less(), nVar, 6, 5, 9); _ASSERT(!b); _ASSERTEQUAL(nVar, 5); b=tc::assign_better(tc::fn_less(), nVar, 3); _ASSERT(b); _ASSERTEQUAL(nVar, 3); b=tc::assign_better(tc::fn_less(), nVar, 5, 2, 8, 0); _ASSERT(b); _ASSERTEQUAL(nVar, 0); } namespace no_adl { template struct has_assign_better_impl final: tc::constant {}; template requires requires(Better&& better, Var&& var, Val&&... val) { tc::assign_better(tc_move_if_owned(better), tc_move_if_owned(var), tc_move_if_owned(val)...);} struct has_assign_better_impl> final: tc::constant {}; template using has_assign_better = has_assign_better_impl>; } #ifndef __EMSCRIPTEN__ static_assert(!no_adl::has_assign_better, int>::value); static_assert(!no_adl::has_assign_better, int, int>::value); static_assert(no_adl::has_assign_better&, int>::value); static_assert(no_adl::has_assign_better&, int, int>::value); #endif static_assert(no_adl::has_assign_better::value); static_assert(no_adl::has_assign_better::value); } ================================================ FILE: tc/algorithm/filter_inplace.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../range/meta.h" #include "../range/range_return.h" #include "../container/container_traits.h" #include "../storage_for.h" #include "restrict_size_decrement.h" namespace tc { template struct range_filter_by_move_element : tc::constant< tc::instance || tc::instance > {}; template struct range_filter; template requires has_efficient_erase::value || has_mem_fn_lower_bound || has_mem_fn_hash_function struct range_filter : tc::noncopyable { static_assert(tc::decayed); private: Cont& m_cont; tc::iterator_t m_itOutputEnd; public: explicit constexpr range_filter(Cont& cont) noexcept : m_cont(cont) , m_itOutputEnd(tc::begin(cont)) {} constexpr range_filter(Cont& cont, tc::iterator_t const& itStart) noexcept : m_cont(cont) , m_itOutputEnd(itStart) {} constexpr ~range_filter() { tc::take_inplace(m_cont, m_itOutputEnd); } constexpr void keep(tc::iterator_t it) & noexcept { #ifdef _CHECKS auto const nDistance = std::distance(m_itOutputEnd, it); _ASSERTE( 0<=nDistance ); auto const rsize = restrict_size_decrement(m_cont, nDistance, nDistance); #endif m_itOutputEnd=m_cont.erase(m_itOutputEnd,it); ++m_itOutputEnd; } /////////////////////////////////////////// // range interface for output range // no deep constness (analog to subrange) constexpr auto begin() const& noexcept { return tc::begin(m_cont); } constexpr auto end() const& noexcept { return m_itOutputEnd; } constexpr void pop_back() & noexcept requires (!has_mem_fn_hash_function) { _ASSERTE( m_itOutputEnd!=tc::begin(m_cont) ); auto const rsize = restrict_size_decrement(m_cont); --m_itOutputEnd; m_itOutputEnd=m_cont.erase(m_itOutputEnd); } }; template struct range_filter : Cont, private tc::noncopyable { static_assert(tc::decayed); Cont& m_contInput; explicit constexpr range_filter(Cont& cont) noexcept : m_contInput(cont) {} constexpr range_filter(Cont& cont, tc::iterator_t const& itStart) noexcept : m_contInput(cont) { this->splice( tc::end(*this), m_contInput, tc::begin(m_contInput), itStart ); } constexpr ~range_filter() { m_contInput=tc_move_always( tc::base_cast(*this) ); } constexpr void keep(tc::iterator_t it) & noexcept { _ASSERTE( it!=tc::end(m_contInput) ); this->splice( tc::end(*this), m_contInput, m_contInput.erase( tc::begin(m_contInput), it ) ); } }; template requires range_filter_by_move_element::value struct range_filter : tc::noncopyable { static_assert(tc::decayed); protected: Cont& m_cont; tc::iterator_t m_itOutput; private: #ifdef _CHECKS tc::iterator_t m_itFirstValid; #endif public: explicit constexpr range_filter(Cont& cont) noexcept : m_cont(cont) , m_itOutput(tc::begin(cont)) #ifdef _CHECKS , m_itFirstValid(tc::begin(cont)) #endif {} explicit constexpr range_filter(Cont& cont, tc::iterator_t itStart) noexcept : m_cont(cont) , m_itOutput(itStart) #ifdef _CHECKS , m_itFirstValid(itStart) #endif {} constexpr ~range_filter() { tc::take_inplace( m_cont, m_itOutput ); } constexpr void keep(tc::iterator_t it) & noexcept { #ifdef _CHECKS // Filter without reordering _ASSERTE( 0<=std::distance(m_itFirstValid, it) ); m_itFirstValid=it; ++m_itFirstValid; #endif if (it != m_itOutput) { // self assignment with r-value-references is not allowed (17.6.4.9) *m_itOutput=tc_move_always(*it); } ++m_itOutput; } /////////////////////////////////// // range interface for output range // no deep constness (analog to subrange) constexpr auto begin() const& noexcept { return tc::begin(m_cont); } constexpr auto end() const& noexcept { return m_itOutput; } constexpr void pop_back() & noexcept { _ASSERTE( tc::begin(m_cont)!=m_itOutput ); --m_itOutput; } template constexpr void emplace_back(Ts&&... ts) & MAYTHROW { _ASSERTE( tc::end(m_cont)!=m_itOutput ); tc::assign_explicit_cast(*m_itOutput, tc_move_if_owned(ts)...); // MAYTHROW ++m_itOutput; } }; ///////////////////////////////////////////////////// // filter_inplace template void filter_inplace(Cont & cont, tc::iterator_t it, Pred pred = Pred()) noexcept(noexcept(tc_invoke(pred, *it))) { for (auto const itEnd = tc::end(cont); it != itEnd; ++it) { if (!tc::explicit_cast(tc_invoke(pred, *it))) { tc::range_filter< tc::decay_t > rngfilter(cont, it); ++it; while (it != itEnd) { // taking further action to destruct *it when returning false is legitimate use case, so do do not enforce const if (tc_invoke(pred, *it)) { rngfilter.keep(it++); // may invalidate it, so move away first } else { ++it; } } break; } } } template void filter_inplace(Cont& cont, Pred&& pred = Pred()) return_MAYTHROW( tc::filter_inplace(cont, tc::begin(cont), tc_move_if_owned(pred)) ) } ================================================ FILE: tc/algorithm/find.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../range/meta.h" #include "../range/iterator_cache.h" #include "../base/scope.h" #include "../base/trivial_functors.h" #include "../storage_for.h" #include "equal.h" namespace tc { namespace no_adl { #ifdef _MSC_VER template struct element_return_type : std::type_identity()))> {}; #endif } #ifdef _MSC_VER // MSVC has problems with decltype in alias templates. template using element_return_type_t = typename no_adl::element_return_type::type; #else template using element_return_type_t = decltype(RangeReturn::pack_no_element(std::declval())); #endif namespace find_first_if_detail { template< typename RangeReturn, IF_TC_CHECKS(bool c_bCheckUnique,) typename Rng > [[nodiscard]] constexpr tc::element_return_type_t find_first_if(Rng&& rng, auto pred) MAYTHROW { if constexpr( RangeReturn::requires_iterator ) { auto const itEnd=tc::end(rng); // MAYTHROW for( auto it=tc::begin(rng) /*MAYTHROW*/; it!=itEnd; ++it /*MAYTHROW*/ ) { if constexpr(std::same_as>) { // tc::front #ifdef _CHECKS if constexpr( c_bCheckUnique ) { // tc::only _ASSERTE( tc_modified(it, ++_) == itEnd ); } #endif return RangeReturn::pack_element(tc_move(it),tc_move_if_owned(rng)); // MAYTHROW } else { decltype(auto) ref = *it; // MAYTHROW if (tc::explicit_cast(tc_invoke(pred, tc::as_const(ref)) /*MAYTHROW*/)) { #ifdef _CHECKS if constexpr( c_bCheckUnique ) { for( auto it2 = tc_modified(it, ++_); it2!=itEnd; ++it2 ) { // hand-rolled loop to avoid dependency to subrange _ASSERTE( !tc::explicit_cast(tc_invoke(pred, tc::as_const(*it2))) ); } } #endif return RangeReturn::pack_element(tc_move(it),tc_move_if_owned(rng),tc_move_if_owned(ref)); // MAYTHROW } } } return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } else { #ifdef _CHECKS if constexpr( c_bCheckUnique ) { std::optional> ot; tc::for_each(tc_move_if_owned(rng), [&](auto&& t) MAYTHROW { if( tc::explicit_cast(tc_invoke(pred, tc::as_const(t)) /*MAYTHROW*/) ) { VERIFYCRITICALPRED(ot, !_).emplace(RangeReturn::template pack_element(tc_move_if_owned(t)) /* MAYTHROW */); } }); // MAYTHROW if( ot ) return *tc_move(ot); } else #endif { tc::storage_for_without_dtor> ot; if( tc::break_ == tc::for_each(tc_move_if_owned(rng), [&](auto&& t) MAYTHROW { if (tc::explicit_cast(tc_invoke(pred, tc::as_const(t)) /*MAYTHROW*/)) { ot.ctor(RangeReturn::template pack_element(tc_move_if_owned(t)) /* MAYTHROW */); return tc::break_; // We assume for_each never throws exceptions after the sink returned break_. } else { return tc::continue_; } }) /* MAYTHROW */ ) { tc_scope_exit { ot.dtor(); }; return *tc_move(ot); } } return RangeReturn::template pack_no_element(); } } } template< typename RangeReturn, typename Rng, typename Pred = tc::identity > [[nodiscard]] constexpr decltype(auto) find_first_if(Rng&& rng, Pred&& pred = Pred()) MAYTHROW { return find_first_if_detail::find_first_if(tc_move_if_owned(rng), tc_move_if_owned(pred)); } template< typename RangeReturn, typename Rng, typename Pred = tc::identity > [[nodiscard]] constexpr decltype(auto) find_unique_if(Rng&& rng, Pred&& pred = Pred()) MAYTHROW { return find_first_if_detail::find_first_if(tc_move_if_owned(rng), tc_move_if_owned(pred)); } template< typename RangeReturn, typename Rng, typename Pred = tc::identity > [[nodiscard]] constexpr decltype(auto) find_last_if(Rng&& rng, Pred pred = Pred()) MAYTHROW { if constexpr( tc::bidirectional_range && tc::common_range ) { auto const itBegin=tc::begin(rng); for( auto it=tc::end(rng); it!=itBegin; ) { --it; if constexpr(std::same_as>) { // tc::back return RangeReturn::pack_element(tc_move(it), tc_move_if_owned(rng)); } else { decltype(auto) ref = *it; if (tc::explicit_cast(tc_invoke(pred, tc::as_const(ref)))) { return RangeReturn::pack_element(tc_move(it), tc_move_if_owned(rng), tc_move_if_owned(ref)); } } } } else { auto const itEnd=tc::end(rng); for( auto it=tc::begin(rng); it!=itEnd; ++it ) { std::array>, 2> aic; // tc::storage_for was too slow in debug builds int iFound = 0; aic[iFound].ctor(it); tc_scope_exit { aic[iFound].dtor(); }; //iFound captured by reference if (tc::explicit_cast(tc_invoke(pred, tc::as_const(**aic[iFound])))) { for (;;) { ++it; if (itEnd==it) break; aic[1 - iFound].ctor(it); tc_scope_exit { aic[1 - iFound].dtor(); }; if (tc_invoke(pred, tc::as_const(**aic[1 - iFound]))) { iFound = 1 - iFound; } } return RangeReturn::pack_element( tc_move_always(*aic[iFound]).m_it_(), // pack_element must not take iterator by value tc_move_if_owned(rng), *tc_move_always(*aic[iFound]) ); } } } return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } namespace no_adl { BOOST_MPL_HAS_XXX_TRAIT_DEF(key_type) } using no_adl::has_key_type; namespace find_first_or_unique_default { template< typename RangeReturn, IF_TC_CHECKS(bool c_bCheckUnique,) typename Rng, typename T > [[nodiscard]] constexpr decltype(auto) find_first_or_unique_impl(std::type_identity, IF_TC_CHECKS(tc::constant,) Rng&& rng, T const& t) MAYTHROW { static_assert( !tc::has_key_type>::value, "Do you want to use tc::cont_find?" ); return find_first_if_detail::find_first_if(tc_move_if_owned(rng), [&](auto const& _) MAYTHROW { return tc::equal_to(_, t); }); } } DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(find_first_or_unique) template< typename RangeReturn, typename Rng, typename T > [[nodiscard]] constexpr decltype(auto) find_unique(Rng&& rng, T&& t) MAYTHROW { return find_first_or_unique(std::type_identity(), IF_TC_CHECKS(tc::constant(),) tc_move_if_owned(rng), tc_move_if_owned(t)); } template< typename RangeReturn, typename Rng, typename T > [[nodiscard]] constexpr decltype(auto) find_first(Rng&& rng, T&& t) MAYTHROW { return find_first_or_unique(std::type_identity(), IF_TC_CHECKS(tc::constant(),) tc_move_if_owned(rng), tc_move_if_owned(t)); } template< typename RangeReturn, typename Rng, typename T > [[nodiscard]] constexpr decltype(auto) find_last(Rng&& rng, T const& t) noexcept { return tc::find_last_if( tc_move_if_owned(rng), [&](auto const& _) noexcept { return tc::equal_to(_, t); } ); } } ================================================ FILE: tc/algorithm/find_closest_if.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "element.h" namespace tc { template< typename Rng, typename It, typename Func > auto for_each_iterator_pair_outwards(Rng&& rng, It itOrigin, bool bSkipSelf, Func func) noexcept -> tc::common_type_t const&>())), tc::constant> { std::array const aitLimit = { tc::begin(rng), tc::end(rng) }; std::array ait = { itOrigin, itOrigin }; if (!bSkipSelf) { _ASSERT(tc::back(aitLimit) != tc::back(ait)); tc_return_if_break(tc::continue_if_not_break(func, tc::begin_next(tc::as_const(ait)))) ++tc::back(ait); } else if(tc::back(aitLimit) != tc::back(ait)) { ++tc::back(ait); } for (;;) { if (tc::front(aitLimit) == tc::front(ait)) { for (; tc::back(ait) != tc::back(aitLimit); ++tc::back(ait)) { tc_return_if_break(tc::continue_if_not_break(func, tc::begin_next(tc::as_const(ait)))) } return tc::constant(); } if (tc::back(aitLimit) == tc::back(ait)) { while (tc::front(ait) != tc::front(aitLimit)) { --tc::front(ait); tc_return_if_break(tc::continue_if_not_break(func, tc::begin_next(tc::as_const(ait)))) } return tc::constant(); } --tc::front(ait); tc_return_if_break(tc::continue_if_not_break(func, tc::as_const(ait))) ++tc::back(ait); } } template < typename RangeReturn, typename Rng, typename It, typename Pred = tc::identity> [[nodiscard]] constexpr decltype(auto) find_closest_if(Rng&& rng, It it, bool const bSkipSelf, Pred pred = Pred()) noexcept { tc::storage_for> oitc; if (tc::break_ == tc::for_each_iterator_pair_outwards(rng, tc_move(it), bSkipSelf, [&](auto const& rngit) noexcept { return tc::for_each(rngit, [&](auto const& it) noexcept { oitc.ctor(it); if (tc::explicit_cast(tc_invoke(pred, tc::as_const(**oitc)))) { return tc::break_; } oitc.dtor(); return tc::continue_; }); })) { tc_scope_exit { oitc.dtor(); }; return RangeReturn::pack_element(oitc->m_it_(), tc_move_if_owned(rng), **tc_move(oitc)); } else { return RangeReturn::pack_no_element(tc_move_if_owned(rng)); } } template < typename RangeReturn, typename Rng, typename Index, typename Pred = tc::identity> [[nodiscard]] decltype(auto) find_closest_if_with_index(Rng&& rng, Index&& n, bool const bSkipSelf, Pred&& pred = Pred()) noexcept { return find_closest_if(tc_move_if_owned(rng), tc::begin_next(rng, tc_move_if_owned(n)), bSkipSelf, tc_move_if_owned(pred)); } } ================================================ FILE: tc/algorithm/find_closest_if.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "find_closest_if.h" #include "../unittest.h" UNITTESTDEF(find_closest_if) { struct IntCompareOnce final : boost::noncopyable { IntCompareOnce(int n) noexcept :m_n(n) { } bool operator==(int const n) const& noexcept { _ASSERT( tc::change(m_bCompared, true) ); return m_n==n; } private: int m_n; bool mutable m_bCompared = false; }; tc_static_auto_constexpr_lambda(find) = [](auto const& rngn, int const iStart, int const nTarget, int const nComparisonsMax) noexcept { int nComparisons = 0; return tc::find_closest_if_with_index(rngn, iStart, /*bSkipSelf*/false, [&](IntCompareOnce const& n) noexcept { _ASSERT(++nComparisons<=nComparisonsMax); return n==nTarget; }); }; for (int iStart = 0; iStart < 4; ++iStart) { for (int nTarget = -1; nTarget < 4; ++nTarget) { int nComparisonsMax = -1==nTarget ? 5 : nTarget(tc::aggregate_tag, 0, 1, 2, 3, 4), iStart, nTarget, nComparisonsMax), -1==nTarget ? std::nullopt : std::make_optional(nTarget)); } } } ================================================ FILE: tc/algorithm/for_each.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../base/utility.h" #include "../range/index_range.h" #include "../range/meta.h" #include "../tuple.h" #include "../base/modified.h" #include "break_or_continue.h" namespace tc { namespace no_adl { template struct common_type_break_or_continue; template<> struct common_type_break_or_continue<> final { using type = tc::constant; }; template struct common_type_break_or_continue, T...> { using type = typename common_type_break_or_continue::type; }; template struct common_type_break_or_continue, T...> { using type = tc::constant; }; template struct common_type_break_or_continue { using type = std::conditional_t< std::is_same, typename common_type_break_or_continue::type>::value, tc::constant, tc::break_or_continue >; }; } template using common_type_break_or_continue_t = typename no_adl::common_type_break_or_continue::type; template concept has_mem_fn_chunk = requires { std::declval().chunk(std::declval()); }; DEFINE_MEM_FN(chunk); namespace void_generator_type_check_no_adl { template struct check_sink_result; template struct check_sink_result { static_assert( !std::is_same::value && !std::is_same>::value, "Functor may return break_, but range does not support it." ); }; template struct check_sink_result { static_assert( std::is_same>::value, "Functor does not always break, but range does (broken range implementation)." ); }; template struct verify_sink_result_impl final : Sink { static_assert(tc::decayed); using guaranteed_break_or_continue = tc::constant; template()(std::declval()...))> constexpr tc::constant operator()(Args&&... args) const& noexcept(noexcept( tc::base_cast(*this)(std::declval()...) )) { check_sink_result(); tc::base_cast(*this)(tc_move_if_owned(args)...); // MAYTHROW return {}; } template requires tc::has_mem_fn_chunk constexpr tc::constant chunk(Rng&& rng) const& noexcept(noexcept( tc::base_cast(*this).chunk(tc_move_if_owned(rng)) )) { check_sink_result(*this).chunk(tc_move_if_owned(rng))), boc>(); tc::base_cast(*this).chunk(tc_move_if_owned(rng)); // MAYTHROW return {}; } }; template struct guaranteed_break_or_continue final { using type = tc::break_or_continue; }; template requires requires { typename std::remove_reference_t::guaranteed_break_or_continue; } struct guaranteed_break_or_continue final { using type = typename std::remove_reference_t::guaranteed_break_or_continue; }; } template using guaranteed_break_or_continue_t = typename void_generator_type_check_no_adl::guaranteed_break_or_continue::type; template requires std::is_same::value || std::is_same>::value constexpr decltype(auto) verify_sink_result(Sink&& sink) noexcept { return tc_move_if_owned(sink); } template constexpr void_generator_type_check_no_adl::verify_sink_result_impl> verify_sink_result(Sink&& sink) noexcept { static_assert( std::is_same, tc::break_or_continue>::value, "Mismatch between range and sink result types (broken range implementation)." ); return {tc_move_if_owned(sink)}; } //------------------------------------------------------------------------------------------------------------------------- // for_each namespace for_each_adl { DEFINE_ADL_TAG_TYPE(adl_tag) } template \ void for_each_impl() noexcept; /*TODO c++20: workaround for c++17*/ namespace for_each_detail { TC_DEFINE_ENUM(EOverload, eoverload, (CHUNK)(ADL)(INVOKERNG)(ADLTAG)(INDEX)(ITERATOR)(NONE)) namespace no_adl { template struct make_break_or_continue; template<> struct make_break_or_continue> { using type = tc::constant; }; template<> struct make_break_or_continue> { using type = tc::constant; }; template<> struct make_break_or_continue { using type = tc::break_or_continue; }; template<> struct make_break_or_continue { using type = tc::constant; }; } template using make_break_or_continue_t = typename no_adl::make_break_or_continue::type; namespace no_adl { template struct select_overload : select_overload {}; #pragma push_macro("SELECT_OVERLOAD_IF") #define SELECT_OVERLOAD_IF(priority, eoverload, requires_clause) \ template requires_clause \ struct select_overload : tc::constant {}; SELECT_OVERLOAD_IF(0, eoverloadCHUNK, TC_FWD(requires tc::has_mem_fn_chunk const&, Rng>)) SELECT_OVERLOAD_IF(1, eoverloadADL, TC_FWD(requires requires { for_each_impl(std::declval(), std::declval()); })) SELECT_OVERLOAD_IF(1, eoverloadINVOKERNG, TC_FWD(requires requires { typename make_break_or_continue_t()(std::declval()))>; })) SELECT_OVERLOAD_IF(2, eoverloadADLTAG, TC_FWD(requires requires { for_each_impl(for_each_adl::adl_tag, std::declval(), std::declval()); })) SELECT_OVERLOAD_IF(2, eoverloadINDEX, requires tc::has_index>) SELECT_OVERLOAD_IF(3, eoverloadITERATOR, requires tc::range_with_iterators) SELECT_OVERLOAD_IF(4, eoverloadNONE, /*none*/) #pragma pop_macro("SELECT_OVERLOAD_IF") } using no_adl::select_overload; } template requires (for_each_detail::eoverloadCHUNK == for_each_detail::select_overload::value) constexpr auto for_each(Rng&& rng, Sink&& sink) return_MAYTHROW( tc_internal_continue_if_not_break(/*do we really need that copy?*/tc::as_const(tc::as_lvalue(tc::decay_copy(sink))).chunk(tc_move_if_owned(rng))) ) template requires (for_each_detail::eoverloadADL == for_each_detail::select_overload::value) constexpr auto for_each(Rng&& rng, Sink&& sink) return_MAYTHROW( for_each_impl(tc_move_if_owned(rng), tc_move_if_owned(sink)) ) template requires (for_each_detail::eoverloadADLTAG == for_each_detail::select_overload::value) constexpr auto for_each(Rng&& rng, Sink&& sink) return_MAYTHROW( for_each_impl(for_each_adl::adl_tag, tc_move_if_owned(rng), tc_move_if_owned(sink)) ) template requires (for_each_detail::eoverloadINVOKERNG == for_each_detail::select_overload::value) constexpr auto for_each(Rng&& rng, Sink&& sink) return_MAYTHROW( tc_internal_continue_if_not_break(tc_move_if_owned(rng)(tc::verify_sink_result()(std::declval()))>>(tc_move_if_owned(sink)))) ) template::value>* = nullptr> constexpr auto for_each(Rng&& rng, Sink&& sink_) noexcept( noexcept(rng.at_end_index(tc::as_lvalue(tc::decay_copy(rng.begin_index())))) && noexcept(rng.increment_index(tc::as_lvalue(rng.begin_index()))) && noexcept(tc::continue_if_not_break(std::declval const&>(), rng.dereference_index(tc::as_lvalue(rng.begin_index())))) ) -> tc::common_type_t const&>(), rng.dereference_index(tc::as_lvalue(rng.begin_index())))), tc::constant> { tc::decay_t const sink = sink_; // do we really need that copy? for (auto i = rng.begin_index(); !rng.at_end_index(i); rng.increment_index(i)) { tc_return_if_break(tc::continue_if_not_break(sink, rng.dereference_index(i))) } return tc::constant(); } template::value>* = nullptr> constexpr auto for_each(Rng&& rng, Sink&& sink_) noexcept( noexcept(tc::end(rng)) && noexcept(++tc::as_lvalue(tc::begin(rng))) && noexcept(tc::continue_if_not_break(std::declval const&>(), *tc::as_lvalue(tc::begin(rng)))) ) -> tc::common_type_t const&>(), *tc::as_lvalue(tc::begin(rng)))), tc::constant> { tc::decay_t const sink = sink_; // do we really need that copy? auto const itEnd = tc::end(rng); for(auto it = tc::begin(rng); it!= itEnd; ++it) { tc_return_if_break(tc::continue_if_not_break(sink, *it)) } return tc::constant(); } namespace for_each_detail { template constexpr auto type_value = std::type_identity{}; template constexpr auto type_value> = tc::constant{}; // If the type is a type list, we turn it into a tuple of type values. // That way `tc::for_each(tc::zip(list1, list2), [](std::type_identity, std::type_identity) { ... })` just works. template constexpr auto type_value> = tc::make_tuple(type_value...); template(), type_value))...>> constexpr R for_each_parameter_pack(boost::mp11::mp_list, Sink const sink) noexcept((noexcept(tc::continue_if_not_break(sink, type_value)) && ...)) { if constexpr (std::is_same, R>::value) { (tc_invoke(sink, type_value), ...); return tc::constant(); } else { auto const boc = tc::continue_if(((tc::continue_ == tc_internal_continue_if_not_break(tc_invoke(sink, type_value))) && ...)); if constexpr (std::is_same, R>::value) { return tc::constant(); } else { return boc; } } } namespace no_adl { template struct tuple_index_sink final { Tuple&& m_tuple; Sink m_sink; template constexpr auto operator()(tc::constant) const& return_decltype_MAYTHROW( tc_invoke(m_sink, tc::get(tc_move_if_owned(m_tuple))) ) }; } using no_adl::tuple_index_sink; } namespace for_each_adl { template constexpr auto for_each_impl(adl_tag_t, std::integer_sequence, Sink&& sink) return_decltype_MAYTHROW( for_each_detail::for_each_parameter_pack(boost::mp11::mp_list...>(), tc_move_if_owned(sink)) ) template constexpr auto for_each_impl(adl_tag_t, boost::mp11::mp_list, Sink&& sink) return_decltype_MAYTHROW( for_each_detail::for_each_parameter_pack(boost::mp11::mp_list(), tc_move_if_owned(sink)) ) template>::value>> TC_REQUIRES_CWG2369_WORKAROUND(!tc::range_with_iterators) constexpr auto for_each_impl(adl_tag_t, Tuple&& tuple, Sink&& sink) return_decltype_MAYTHROW( for_each_detail::for_each_parameter_pack(IndexList(), for_each_detail::tuple_index_sink>{tc_move_if_owned(tuple), tc_move_if_owned(sink)}) ) template::value>* = nullptr> constexpr auto for_each_impl(adl_tag_t, Rng&& rng, Sink&& sink_) noexcept( noexcept(tc::end(rng)) && noexcept(++tc::as_lvalue(tc::begin(rng))) && noexcept(tc::continue_if_not_break(std::declval const&>(), tc_move_always(rng.extract(tc::begin(rng)).value()))) ) -> tc::common_type_t const&>(), tc_move_always(rng.extract(tc::begin(rng)).value()))), tc::constant> { tc::decay_t const sink = sink_; // do we really need that copy? auto it = tc::begin(rng); auto const itEnd = tc::end(rng); while (it != itEnd) { auto itNext = tc_modified(it, ++_); tc_return_if_break(tc::continue_if_not_break(sink, tc_move_always(rng.extract(it).value()))) it = itNext; } return tc::constant(); } } namespace range_output_tuple_impl::no_adl { struct fn_range_output final { template boost::mp11::mp_unique> operator()(Ts&&...) const& { return {}; // unevaluated, not declaration only because of return_invoke workaround } }; } namespace range_output_t_adl { template auto range_output_t_impl(adl_tag_t, boost::mp11::mp_list) -> boost::mp11::mp_unique)>...>>; // declaration only template) auto range_output_t_impl(adl_tag_t, Tuple&& tpl) -> decltype(tc_apply(range_output_tuple_impl::no_adl::fn_range_output(), std::declval())); // declaration only } namespace make_lazy_adl { template, tc::make_lazy>>* = nullptr > constexpr auto for_each_impl(Lazy&& lazy, Sink&& sink) return_decltype_MAYTHROW( tc::for_each(tc_move_if_owned(lazy)(),tc_move_if_owned(sink)) ) } TC_HAS_EXPR(for_each, (Rng)(Sink), tc::for_each(std::declval(), std::declval())) } #define TC_RETURN_BREAK_OR_CONTINUE_SEQUENCE_TYPE(state, _, elem) \ decltype(elem), #define TC_RETURN_BREAK_OR_CONTINUE_SEQUENCE_EVAL(state, _, i, elem) \ tc_return_if_break_impl( \ tc::implicit_cast(tc::constant()), \ elem \ ) #define tc_return_break_or_continue_impl(exprseq) \ using BreakOrContinue = tc::common_type_break_or_continue_t< \ BOOST_PP_SEQ_FOR_EACH(TC_RETURN_BREAK_OR_CONTINUE_SEQUENCE_TYPE, _, exprseq) \ tc::constant \ >; \ BOOST_PP_SEQ_FOR_EACH_I(TC_RETURN_BREAK_OR_CONTINUE_SEQUENCE_EVAL, _, exprseq) \ if constexpr(!std::same_as>) { \ return tc::implicit_cast(tc::constant()); \ } #define tc_return_break_or_continue(exprseq) \ tc_return_break_or_continue_impl(exprseq) ================================================ FILE: tc/algorithm/for_each.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "../base/assert_defs.h" #include "../unittest.h" #include "../base/ref.h" #include "../range/adjacent_adaptor.h" #include "../range/ordered_pairs.h" #include "../container/insert.h" #include "for_each_xxx.h" #include STATICASSERTEQUAL(tc_internal_continue_if_not_break(tc::break_), tc::break_); STATICASSERTEQUAL(tc_internal_continue_if_not_break(tc::continue_), tc::continue_); STATICASSERTSAME(decltype(tc_internal_continue_if_not_break(tc::break_)), tc::break_or_continue); STATICASSERTSAME(decltype(tc_internal_continue_if_not_break(tc::continue_)), tc::break_or_continue); STATICASSERTSAME(decltype(tc_internal_continue_if_not_break(tc::constant())), tc::constant); STATICASSERTSAME(decltype(tc_internal_continue_if_not_break(tc::constant())), tc::constant); STATICASSERTSAME(decltype(tc_internal_continue_if_not_break(23)), tc::constant); STATICASSERTSAME(decltype(tc_internal_continue_if_not_break(void())), tc::constant); //---- for_each --------------------------------------------------------------------------------------------------------------- namespace { struct all_called_mock final { all_called_mock() noexcept : m_expect(0), m_index(0), m_break_at(0), m_copyed_or_moved_from(false) {} all_called_mock(tc::vector const& v, std::size_t break_at = 0, bool expect_break = true) noexcept : m_expect(v), m_index(0), m_break_at((break_at == 0) ? v.size() : break_at), m_expect_break((break_at != 0) && expect_break), m_copyed_or_moved_from(false) {} all_called_mock(all_called_mock const& copy) noexcept : m_expect(copy.m_expect), m_index(copy.m_index), m_break_at(copy.m_break_at), m_expect_break(copy.m_expect_break), m_copyed_or_moved_from(false) { copy.m_copyed_or_moved_from = true; } all_called_mock(all_called_mock&& move) noexcept : m_expect(move.m_expect), m_index(move.m_index), m_break_at(move.m_break_at), m_expect_break(move.m_expect_break), m_copyed_or_moved_from(false) { move.m_copyed_or_moved_from = true; } ~all_called_mock() { mock_reset(); } void mock_reset(tc::vector const& v = tc::vector(), std::size_t const break_at = 0, bool expect_break = true) & noexcept { if(!m_copyed_or_moved_from && !(m_index == tc::min(m_expect.size(), (m_expect_break) ? m_break_at + 1 : m_expect.size()))) { TEST_OUTPUT( << "unexpectedly terminated before index " << m_index << " went to the expected index " << tc::min(m_expect.size(), m_break_at + 1) << '\n'); _ASSERTEQUAL(m_index, tc::min(m_expect.size(), m_break_at + 1)); } m_index = 0; m_expect = v; m_break_at = (break_at == 0) ? v.size() : break_at; m_expect_break = (break_at != 0) && expect_break; m_copyed_or_moved_from = false; } tc::break_or_continue operator()(int const val) const& noexcept { if (m_copyed_or_moved_from) { TEST_OUTPUT(<< "used copyed or moved consumer for real work!\n"); } if(!(m_index < m_expect.size())) { TEST_OUTPUT( << "unexpectedly called for index " << m_index << " when expect has size " << m_expect.size() << '\n'); _ASSERT(m_index < m_expect.size()); } if (val != m_expect[m_index]) { TEST_OUTPUT( << "unexpected value " << val << " at index " << m_index << ", should be " << m_expect[m_index] << '\n'); _ASSERTEQUAL(val, m_expect[m_index]); } ++m_index; return tc::continue_if(m_index <= m_break_at); } private: tc::vector m_expect; mutable std::size_t m_index; std::size_t m_break_at; bool m_expect_break; mutable bool m_copyed_or_moved_from; // so that the copy ctor can mark the copyied from instance }; all_called_mock g_mock; void foo(int const i) noexcept { g_mock(i); } //----------------------------------------------------------------------------------------------------------------------------- UNITTESTDEF( for_each ) { using std::placeholders::_1; TEST_init_hack(tc::vector, int, v, {1,2,3,4,5,6,7,8,9,10}); TEST_init_hack(tc::vector, int, exp, {1,2,3,4,5,6,7,8,9,10}); auto gv = tc::make_generator_range(v); // call with functor object. tc::for_each(v, all_called_mock(exp)); tc::for_each(gv, all_called_mock(exp)); // test if break_ works tc::for_each(gv, all_called_mock(exp, 5)); // call with function g_mock.mock_reset(exp); tc::for_each(v, foo); //g_mock.mock_reset(exp); tc::for_each(gv, foo); g_mock.mock_reset(exp); tc::for_each(gv, tc_fn(foo)); //call with pointer to function g_mock.mock_reset(exp); tc::for_each(v, &foo); //g_mock.mock_reset(exp); tc::for_each(gv, &foo); // call with lambda g_mock.mock_reset(exp); tc::for_each(v, [](int const i) noexcept { g_mock(i); }); g_mock.mock_reset(exp); tc::for_each(gv, [](int const i) noexcept { g_mock(i); }); // Todo: call with mem func, std::function and bind } //---- test break behavior --------------------------------------------------------------------------------------------------- struct iterate final { tc::break_or_continue generator_break_consumer_break(tc::function_ref< tc::break_or_continue(int) noexcept > func) const& noexcept { for(int i = 1; i<11; ++i) { if(func(i) == tc::break_) { return tc::break_; } } return tc::continue_; } tc::break_or_continue generator_break_consumer_nobreak(tc::function_ref func) const& noexcept { for(int i = 1; i<11; ++i) { func(i); } return tc::continue_; } void generator_nobreak_consumer_nobreak(tc::function_ref func) const& noexcept { for(int i = 1; i<11; ++i) { func(i); } return; } void generator_nobreak_consumer_break_correct(tc::function_ref< tc::break_or_continue(int) noexcept > func) const& noexcept { for(int i = 1; i<11; ++i) { if(func(i) == tc::break_) { return; } } return; } void generator_nobreak_consumer_break_incorrect(tc::function_ref< tc::break_or_continue(int) noexcept > func) const& noexcept { for(int i = 1; i<11; ++i) { func(i); } return; } }; struct consumer_break /*final*/ { consumer_break(tc::vector const& v, std::size_t break_at = 0, bool expect_break = true) noexcept : m_mock(v, break_at, expect_break) {} tc::break_or_continue operator()(int const i) const& noexcept { return m_mock(i); } private: all_called_mock m_mock; }; struct consumer_nobreak /*final*/ { consumer_nobreak(tc::vector const& v, std::size_t break_at = 0, bool expect_break = true) noexcept : m_mock(v, break_at, expect_break) {} ~consumer_nobreak() {} void operator()(int const i) const& noexcept { m_mock(i); } private: all_called_mock m_mock; }; //----------------------------------------------------------------------------------------------------------------------------- UNITTESTDEF( break_behavior ) { TEST_init_hack(tc::vector, int, exp, {1,2,3,4,5,6,7,8,9,10}); // call on various implicit ranges tc::for_each([](auto&& func) noexcept { return iterate().generator_break_consumer_break(tc_move_if_owned(func)); }, consumer_break(exp, 4)); tc::for_each([](auto&& func) noexcept { return iterate().generator_break_consumer_nobreak(tc_move_if_owned(func)); }, consumer_nobreak(exp, 5, false)); tc::for_each([](auto&& func) noexcept { iterate().generator_nobreak_consumer_nobreak(tc_move_if_owned(func)); }, consumer_nobreak(exp, 6, false)); // these two are undefined and must not compile // tc::for_each([](auto&& func) noexcept { iterate().generator_nobreak_consumer_break_correct(tc_move_if_owned(func)); }, all_called_mock(exp, 7)); // tc::for_each([](auto&& func) noexcept { iterate().generator_nobreak_consumer_break_incorrect(tc_move_if_owned(func)); }, all_called_mock(exp, 8, false)); } UNITTESTDEF(for_each_adjacent_tuple_deref) { struct lr_overloads final{ std::array m_n; lr_overloads() noexcept { tc::at(m_n, 0) = 0; tc::at(m_n, 1) = 0; tc::at(m_n, 2) = 0; } void operator()(int const&, int const&, int const&) & noexcept { ++tc::at(m_n, 0);} void operator()(int&&, int const&, int const&) & noexcept { ++tc::at(m_n, 1); } void operator()(int&&, int&&, int&&) & noexcept { ++tc::at(m_n, 2); } }; tc::vector vecn{0,0,0,0,0}; tc::for_each( tc::adjacent<3>(vecn), [](int& n0, int& n1, int& n2) noexcept { ++n0; ++n1; ++n2; } ); TEST_RANGE_EQUAL( vecn, tc_as_constexpr(tc::make_array(tc::aggregate_tag, 1,2,3,2,1)) ); int nTransforms = 0; tc::for_each( tc::adjacent<3>( tc::transform( vecn, [&](int const n) noexcept {++nTransforms; return n;} ) ), [](int n0, int n1, int n2) noexcept { ++n0; ++n1; ++n2; } ); _ASSERTEQUAL(nTransforms,5); { lr_overloads overloads; tc::for_each( tc::adjacent<3>(vecn), std::ref(overloads) ); TEST_RANGE_EQUAL(overloads.m_n, tc_as_constexpr(tc::make_array(tc::aggregate_tag, 3,0,0))); } { lr_overloads overloads; tc::for_each( tc::adjacent<3>( tc::transform(vecn, [](int const n) noexcept {return n;}) ), std::ref(overloads) ); TEST_RANGE_EQUAL(overloads.m_n, tc_as_constexpr(tc::make_array(tc::aggregate_tag, 0,2,1))); } } UNITTESTDEF(ordered_pairs) { _ASSERT(tc::equal( tc::ordered_pairs(tc::make_array(tc::aggregate_tag, 1,2,3,4)), tc::make_array(tc::aggregate_tag, tc::make_tuple(1, 2), tc::make_tuple(1, 3), tc::make_tuple(1, 4), tc::make_tuple(2, 3), tc::make_tuple(2, 4), tc::make_tuple(3, 4) ) )); _ASSERT(tc::empty(tc::ordered_pairs(tc::make_empty_range()))); _ASSERT(tc::empty(tc::ordered_pairs(tc::single(1)))); } //----------------------------------------------------------------------------------------------------------------------------- STATICASSERTSAME(TC_FWD(tc::range_output_t>), TC_FWD(boost::mp11::mp_list)); STATICASSERTSAME(TC_FWD(tc::range_output_t&>), TC_FWD(boost::mp11::mp_list)); STATICASSERTSAME(TC_FWD(tc::range_output_t>), TC_FWD(boost::mp11::mp_list)); STATICASSERTSAME(TC_FWD(tc::range_output_t&>), TC_FWD(boost::mp11::mp_list)); STATICASSERTSAME(TC_FWD(tc::range_output_t&>), TC_FWD(boost::mp11::mp_list)); STATICASSERTSAME(TC_FWD(tc::range_output_t>), TC_FWD(boost::mp11::mp_list)); // Beware! pair has semantics of range [first, second[ UNITTESTDEF(for_each_tuple) { auto const CheckTuple = [](auto&& tpl) noexcept { int i = 0; tc::for_each(tc_move_if_owned(tpl), [&](auto&& x) { switch_no_default(i++) { #pragma push_macro("CASE") #define CASE(k, type, val) \ case k: \ if constexpr(std::same_as, tc::remove_rvalue_reference_t>) { \ _ASSERTEQUAL(x, TC_FWD(val)); \ } else { \ _ASSERTFALSE; \ } \ break; CASE(0, int, 3) CASE(1, double, tc::as_lvalue(1.4)) CASE(2, int, 59) CASE(3, char, std::move('f')) #pragma pop_macro("CASE") } }); _ASSERTEQUAL(i, tc::size(tpl)); }; CheckTuple(tc::tuple{3, tc::as_lvalue(1.4), 59, std::move('f')}); CheckTuple(std::tuple{3, tc::as_lvalue(1.4), 59, std::move('f')}); CheckTuple(std::pair{3, tc::as_lvalue(1.4)}); } //----------------------------------------------------------------------------------------------------------------------------- UNITTESTDEF(for_each_mp_list) { auto i = 0; tc::for_each(boost::mp11::mp_list{}, [&](std::type_identity) { switch_no_default(i) { case 0: _ASSERT(std::same_as); break; case 1: _ASSERT(std::same_as); break; case 2: _ASSERT(std::same_as); break; } ++i; }); i = 0; tc::for_each(tc::zip(boost::mp11::mp_list{}, boost::mp11::mp_list{}), [&](std::type_identity, std::type_identity) { switch_no_default(i) { case 0: _ASSERT(std::same_as); break; case 1: _ASSERT(std::same_as); break; case 2: _ASSERT(std::same_as); break; } ++i; }); i = 0; tc::for_each(tc::enumerate(boost::mp11::mp_list{}), [&](tc::constant, std::type_identity) { _ASSERTEQUAL(I, i); switch_no_default(i) { case 0: _ASSERT(std::same_as); break; case 1: _ASSERT(std::same_as); break; case 2: _ASSERT(std::same_as); break; } ++i; }); } } ================================================ FILE: tc/algorithm/for_each_xxx.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../array.h" #include "filter_inplace.h" #include "restrict_size_decrement.h" namespace tc { ///////////////////////////////////////////////////// // may_remove_current namespace no_adl { template struct [[nodiscard]] may_remove_current_impl final { // TODO VS2019: if constexpr does not work well in lambda in VS15.8.0 tc::reference_or_value m_rng; constexpr explicit may_remove_current_impl(Rng&& rng) noexcept: m_rng(tc::aggregate_tag, tc_move_if_owned(rng)) {} template constexpr auto operator()(Func func) /* no & */ MAYTHROW -> tc::common_type_t> { auto it=tc::begin(*m_rng); auto const itEnd=tc::end(*m_rng); while( it!=itEnd ) { auto const rsize = restrict_size_decrement(*m_rng, 0, 1); tc_return_if_break(tc::continue_if_not_break(func, *it++)) } return tc::constant(); } }; } // enable_if to ensure that removal preserves iterators would be nice, but is difficult for adapted ranges. template< typename Rng > [[nodiscard]] constexpr auto may_remove_current(Rng&& rng) noexcept code_return_decltype( static_assert( !tc::range_filter_by_move_element< std::remove_reference_t >::value );, no_adl::may_remove_current_impl(tc_move_if_owned(rng)) ) } ================================================ FILE: tc/algorithm/interleave_ranges.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../range/subrange.h" #include "../range/filter_adaptor.h" #include "../range/iota_range.h" #include "../container/container.h" #include "empty.h" #include "element.h" #include "filter_inplace.h" #include namespace tc { namespace no_adl { template using RangeView = decltype(*std::declval>()); template>::value> struct SIteratorView; template struct SIteratorView : std::conditional_t>::value, tc::copyable, tc::noncopyable> { static_assert( tc::stable_index_on_move> ); explicit SIteratorView(tc::iterator_t const& itrng) noexcept : m_rng(tc::aggregate_tag, *itrng) , m_idx(tc::begin_index(*m_rng)) {} void increment_index() & noexcept { tc::increment_index(*m_rng, m_idx); } bool empty() const& noexcept { return tc::at_end_index(*m_rng, m_idx); } private: static decltype(auto) dereference_(auto&& self) return_MAYTHROW( tc::dereference_index(*tc_move_if_owned(self).m_rng, tc_move_if_owned(self).m_idx) ) public: RVALUE_THIS_OVERLOAD_MOVABLE_MUTABLE_REF(dereference) private: tc::reference_or_value> m_rng; tc::index_t> m_idx; }; template struct SIteratorView // Stashing iterator : private tc::iterator_t , public SIteratorView { explicit SIteratorView(tc::iterator_t itrng) noexcept : tc::iterator_t(tc_move(itrng)) , SIteratorView(tc::base_cast>(*this)) {} }; template struct interleave_ranges_index { tc::vector> m_vecview; std::size_t m_nLast; static_assert(!tc::is_stashing_element>::value || std::is_copy_constructible>::value); interleave_ranges_index(tc::empty_range) noexcept : m_nLast(0) {} interleave_ranges_index(RngRng const& rng) noexcept : tc_member_init(m_vecview, tc::filter( tc::make_range_of_iterators(rng), [](auto const& it) noexcept { return !tc::empty(*it); } )) {} }; template struct interleave_ranges_adaptor : tc::range_adaptor_base_range , tc::range_iterator_from_index< interleave_ranges_adaptor, interleave_ranges_index > { private: using this_type = interleave_ranges_adaptor; public: using typename this_type::range_iterator_from_index::tc_index; static constexpr bool c_bHasStashingIndex = true; Less m_less; template interleave_ranges_adaptor(RngRng_&& rngrng, Less_&& less) noexcept : tc::range_adaptor_base_range(tc::aggregate_tag, tc_move_if_owned(rngrng)) , m_less(tc_move_if_owned(less)) {} constexpr auto greater() const& noexcept { return tc::reverse_binary_rel(tc::projected(m_less, tc_mem_fn(.dereference))); } constexpr void prepare_index(tc_index& idx) const& noexcept { tc_auto_cref(greater, this->greater()); idx.m_nLast = tc::size(idx.m_vecview); do { boost::range::pop_heap(tc::begin_next(idx.m_vecview, idx.m_nLast), greater); --idx.m_nLast; } while (0 < idx.m_nLast && !greater(tc::front(idx.m_vecview), tc::at(idx.m_vecview,idx.m_nLast) )); } STATIC_FINAL_MOD(constexpr, begin_index)() const& noexcept -> tc_index { tc_index idx(this->base_range()); if (!this->at_end_index(idx)) { boost::range::make_heap(idx.m_vecview, this->greater()); prepare_index(idx); } return idx; } STATIC_FINAL_MOD(constexpr, end_index)() const& noexcept -> tc_index { return tc::empty_range(); } STATIC_FINAL_MOD(constexpr, at_end_index)(tc_index const& idx) const& noexcept -> bool { return tc::empty(idx.m_vecview); } STATIC_FINAL_MOD(constexpr, increment_index)(tc_index& idx) const& noexcept -> void { _ASSERTE(!this->at_end_index(idx)); tc_auto_cref(greater, this->greater()); tc::filter_inplace(idx.m_vecview, tc::begin_next(idx.m_vecview, idx.m_nLast), [](auto& view) noexcept{ view.increment_index(); return !tc::empty(view); }); tc::for_each( tc::iota(idx.m_nLast, tc::size(idx.m_vecview)), [&](auto const n) noexcept { boost::range::push_heap(tc::begin_next(idx.m_vecview, n+1), greater); } ); if (!this->at_end_index(idx)) { prepare_index(idx); } } STATIC_FINAL_MOD(static constexpr, dereference_index)(auto&& idx) noexcept { return tc::transform( tc::begin_next(tc_move_if_owned(idx).m_vecview, tc_move_if_owned(idx).m_nLast), tc_mem_fn(.dereference) ); } }; } template auto interleave_ranges(RngRng&& rngrng, Less&& less = Less()) { return no_adl::interleave_ranges_adaptor>(tc_move_if_owned(rngrng), tc_move_if_owned(less)); } } ================================================ FILE: tc/algorithm/longest_common_prefix.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "equal.h" namespace tc { template< typename RangeReturn, typename RngLhs, typename RngRhs, typename Pred=tc::fn_equal_to_or_parse_match> [[nodiscard]] constexpr decltype(auto) longest_common_prefix(RngLhs&& rnglhs, RngRhs&& rngrhs, Pred pred=Pred()) MAYTHROW { static_assert(RangeReturn::allowed_if_always_has_border); tc_auto_cref(itlhsEnd, tc::end(rnglhs)); tc_auto_cref(itrhsEnd, tc::end(rngrhs)); auto itlhs=tc::begin(rnglhs); auto itrhs=tc::begin(rngrhs); while(itlhs < itlhsEnd && itrhs < itrhsEnd && tc_invoke(pred, *itlhs, *itrhs)) { ++itlhs; ++itrhs; } return std::make_pair( RangeReturn::pack_border(itlhs, tc_move_if_owned(rnglhs)), RangeReturn::pack_border(itrhs, tc_move_if_owned(rngrhs)) ); } template< typename RangeReturn, typename RngLhs, typename RngRhs, typename Pred=tc::fn_equal_to_or_parse_match> [[nodiscard]] constexpr decltype(auto) longest_common_prefix_lhs(RngLhs&& rnglhs, RngRhs&& rngrhs, Pred pred=Pred()) MAYTHROW { static_assert(RangeReturn::allowed_if_always_has_border); tc_auto_cref(itlhsEnd, tc::end(rnglhs)); auto itlhs=tc::begin(rnglhs); tc::for_each(rngrhs, [&](auto const& rhs) noexcept { if (itlhs!=itlhsEnd && tc_invoke(pred,*itlhs, rhs)) { ++itlhs; return tc::continue_; } else { return tc::break_; } }); return RangeReturn::pack_border(itlhs, tc_move_if_owned(rnglhs)); } } ================================================ FILE: tc/algorithm/longest_common_prefix.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "longest_common_prefix.h" #include "../unittest.h" namespace { UNITTESTDEF( longest_common_prefix ) { tc_static_auto_constexpr_lambda(CheckLCP) = [](auto const& strLhs, auto const& strRhs, auto const& strPrefix, auto const& strSuffixLhs, auto const& strSuffixRhs) noexcept { tc_auto_cref(pairstrstrPrefix, tc::longest_common_prefix(strLhs, strRhs)); _ASSERT(tc::equal(pairstrstrPrefix.first, pairstrstrPrefix.second)); _ASSERT(tc::equal(pairstrstrPrefix.first, strPrefix)); tc_auto_cref(pairstrstrSuffix, tc::longest_common_prefix(strLhs, strRhs)); _ASSERT(tc::equal(pairstrstrSuffix.first, strSuffixLhs)); _ASSERT(tc::equal(pairstrstrSuffix.second, strSuffixRhs)); }; CheckLCP("abcd", "ab", "ab", "cd", ""); CheckLCP("ac", "abcd", "a", "c", "bcd"); CheckLCP("x", "abcd", "", "x", "abcd"); } } ================================================ FILE: tc/algorithm/minmax.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/conditional.h" #include "../base/change.h" #include "size.h" namespace tc { namespace no_adl { template struct TC_EMPTY_BASES fn_best : private std::remove_cvref_t { constexpr fn_best() noexcept = default; template constexpr fn_best(BetterRef&& better) noexcept : std::remove_cvref_t(tc_move_if_owned(better)) {} template [[nodiscard]] constexpr decltype(auto) operator()(T&& t) const& noexcept { return tc_move_if_owned(t); } template [[nodiscard]] constexpr decltype(auto) operator()(T0&& t0, T1&& t1, Args&&... args) const& noexcept { // analogous to std::min/std::max: if equivalent, return the first parameter auto b = tc_invoke(static_cast const&>(*this), tc::as_const(t1), tc::as_const(t0)); if constexpr (std::is_same, decltype(b)>::value) { // t1 is better return operator()(tc_move_if_owned(t1), tc_move_if_owned(args)...); } else if constexpr (std::is_same, decltype(b)>::value) { // t0 is better or equal return operator()(tc_move_if_owned(t0), tc_move_if_owned(args)...); } else { STATICASSERTSAME(decltype(b), bool); return tc_conditional_prvalue_as_val( b, /*t1 is better*/operator()(tc_move_if_owned(t1), tc_move_if_owned(args)...), /*t0 is better or equal*/operator()(tc_move_if_owned(t0), tc_move_if_owned(args)...) ); } } }; } using no_adl::fn_best; using fn_min = fn_best; using fn_max = fn_best; template [[nodiscard]] constexpr auto best(Better&& better, Args&&... args) return_decltype_allow_xvalue_noexcept( fn_best>(tc_move_if_owned(better))(tc_move_if_owned(args)...) ) template [[nodiscard]] constexpr auto min(Args&&... args) return_decltype_allow_xvalue_noexcept( best(tc::fn_less(), tc_move_if_owned(args)...) ) template [[nodiscard]] constexpr auto max(Args&&... args) return_decltype_allow_xvalue_noexcept( best(tc::fn_greater(), tc_move_if_owned(args)...) ) } ================================================ FILE: tc/algorithm/partition_iterator.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../container/insert.h" #include "../static_vector.h" #include "../interval_types.h" #include "../range/reverse_adaptor.h" #include #include #include #include #include namespace tc { namespace iterator { template constexpr It middle_point(It const&, It const&) noexcept; namespace adl { // default forward iterator implementation template requires true // to make it more constrained than tc::iterator::middle_point It middle_point( It const& itBegin, It const& itEnd ) noexcept { // SEnumerateGapConstraints::SEnumerateGapConstraints calls intersect on transforms of counting ranges of tree iterators // intersect calls tc::upper_bound // TODO efficient implementation for tree iterators // static_assert(!std::is_same::value, "provide more efficient middle_point or apply linear search"); return itBegin; } template< typename It, typename UnaryPred > std::reverse_iterator middle_point(std::reverse_iterator const& itBegin, std::reverse_iterator const& itEnd ) noexcept { static_assert(!std::is_same::value, "This code has never been tested"); return std::reverse_iterator( tc::iterator::middle_point( itEnd.base(), itBegin.base() ) ); } // NodeBase is a base type of the multi_index_container's node (internal data // structure used to store elements). #if defined(BOOST_MULTI_INDEX_ENABLE_SAFE_MODE) template< typename AugmentPolicy, typename NodeBase > boost::multi_index::safe_mode::safe_iterator< boost::multi_index::detail::bidir_node_iterator > > middle_point( boost::multi_index::safe_mode::safe_iterator< boost::multi_index::detail::bidir_node_iterator > > itBegin, boost::multi_index::safe_mode::safe_iterator< boost::multi_index::detail::bidir_node_iterator > > itEnd ) #else template< typename AugmentPolicy, typename NodeBase > boost::multi_index::detail::bidir_node_iterator > middle_point( boost::multi_index::detail::bidir_node_iterator > itBegin, boost::multi_index::detail::bidir_node_iterator > itEnd ) noexcept #endif { using node_type = boost::multi_index::detail::ordered_index_node; using TNodeVector = typename tc::static_vector< node_type*, 2* // 2*log(N) is maximum hight of RB tree (CHAR_BIT*sizeof(std::size_t)-3) // 2^3==8 roughly minimum size of node >; // The parent of the root of the tree is a special header node (representing end()) whose // parent is again the root node. tc_static_auto_constexpr_lambda(PathToRoot) = []( node_type* pnode ) noexcept ->TNodeVector { TNodeVector vecpnode; node_type* pnodeParent=node_type::from_impl(pnode->parent()); for(;;) { tc::cont_emplace_back(vecpnode, pnode); node_type* pnodeGrandparent=node_type::from_impl(pnodeParent->parent()); if(pnode==pnodeGrandparent) return vecpnode; // abort if pnode is root pnode=pnodeParent; pnodeParent=pnodeGrandparent; } }; TNodeVector vecpnodeBegin=PathToRoot(itBegin.get_node()); TNodeVector vecpnodeEnd=PathToRoot(tc_modified(itEnd, --_).get_node()); _ASSERTEQUAL( tc::back(vecpnodeBegin), tc::back(vecpnodeEnd) ); // both paths terminate at the root node_type* pnodeCommon=*tc_modified( boost::mismatch( tc::reverse(vecpnodeBegin), tc::reverse(vecpnodeEnd) ).first, --_ ); // or second, same thing #if defined(BOOST_MULTI_INDEX_ENABLE_SAFE_MODE) return boost::multi_index::safe_mode::safe_iterator >>(pnodeCommon, tc::as_mutable_ptr(itBegin.owner())); #else return boost::multi_index::detail::bidir_node_iterator >(pnodeCommon); #endif } template constexpr It middle_point_dispatch( It const& itBegin, It const& itEnd, boost::iterators::forward_traversal_tag ) noexcept { return middle_point(itBegin,itEnd); } // default random-access iterator implementation template constexpr It middle_point_dispatch( It const& itBegin, It const& itEnd, boost::iterators::random_access_traversal_tag ) noexcept { return itBegin+(itEnd-itBegin)/2; } } template [[nodiscard]] constexpr It middle_point( It const& itBegin, It const& itEnd ) noexcept { return adl::middle_point_dispatch( itBegin, itEnd, typename boost::iterator_traversal::type() ); } template [[nodiscard]] constexpr It internal_partition_point( It itBegin, It itEnd, UnaryPred pred ) noexcept { #ifdef _DEBUG /* is pred a partitioning? All true must be before all false. */ It itPartitionPoint = itBegin; while( itPartitionPoint!=itEnd && pred(tc::as_const(itPartitionPoint)) ) ++itPartitionPoint; for( It itRest=itPartitionPoint; itRest!=itEnd; ++itRest ) { _ASSERT( !tc::explicit_cast(pred(tc::as_const(itRest))) ); } #endif while( itBegin!=itEnd ) { It itMid = iterator::middle_point( itBegin, itEnd ); // may return any itMid in [itBegin,itEnd[ _ASSERTDEBUG(itMid != itEnd); if( pred(tc::as_const(itMid)) ) { itBegin=tc_move(itMid); ++itBegin; } else { itEnd=tc_move(itMid); } } // we have already found the partition point; compare result _ASSERTDEBUGEQUAL(itBegin, itPartitionPoint); return itBegin; } template [[nodiscard]] constexpr It partition_point( It itBegin, It itEnd, UnaryPred pred ) noexcept { return internal_partition_point( tc_move(itBegin), tc_move(itEnd), [&pred](It it) noexcept { return pred(tc::as_const(*it)); } ); } template [[nodiscard]] It partition_pair( It itBegin, It itEnd, UnaryPred pred ) noexcept { _ASSERT( itBegin!=itEnd ); --itEnd; return internal_partition_point( tc_move(itBegin), tc_move(itEnd), [&pred](It it) noexcept { return pred(tc::as_const(*it),tc::as_const(*tc_modified(it, ++_))); } ); } //////////////////////////////////////////////////////////////////////// // Iterator functions forwarding to partition_point template [[nodiscard]] It lower_bound(It itBegin,It itEnd,Value const& val,UnaryPredicate pred = {}) noexcept { return iterator::partition_point( itBegin, itEnd, [&](auto const& _) noexcept { return pred(_, val); } ); } template [[nodiscard]] It upper_bound(It itBegin,It itEnd,Value const& val,UnaryPredicate&& pred = {}) noexcept { return iterator::partition_point(itBegin,itEnd,[&](auto const& _) noexcept { return !pred(val, _); }); } template [[nodiscard]] std::pair equal_range(It itBegin,It itEnd,Value const& val,SortPredicate pred = {}) noexcept { // Construct std::pair initialized so that transform_iterator functor // does have to be neither default-constructible nor assignable. This is non-standard conformant, // but may be practical. It itEqualBegin=iterator::lower_bound(itBegin,itEnd,val,pred); return std::pair( itEqualBegin, iterator::upper_bound(itEqualBegin,itEnd,val,pred) ); } } using namespace iterator; using iterator::middle_point; } ================================================ FILE: tc/algorithm/partition_range.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "partition_iterator.h" #include "../range/subrange.h" namespace tc { //////////////////////////////////////////////////////////////////////// // Range functions namespace range { template [[nodiscard]] constexpr decltype(auto) partition_point(Rng&& rng, UnaryPredicate&& pred) noexcept { static_assert( RangeReturn::allowed_if_always_has_border ); return tc_rewrap_temporary(Rng&&, RangeReturn::pack_border( iterator::partition_point(tc::begin(tc_unwrap_temporary(rng)), tc::end(tc_unwrap_temporary(rng)), tc_move_if_owned(pred)), tc_unwrap_temporary(tc_move_if_owned(rng)) )); } template [[nodiscard]] decltype(auto) lower_bound(Rng&& rng, Value const& val, SortPredicate&& pred = {}) noexcept { static_assert( RangeReturn::allowed_if_always_has_border ); return tc_rewrap_temporary(Rng&&, RangeReturn::pack_border( iterator::lower_bound(tc::begin(tc_unwrap_temporary(rng)), tc::end(tc_unwrap_temporary(rng)), val, tc_move_if_owned(pred)), tc_unwrap_temporary(tc_move_if_owned(rng)) )); } template [[nodiscard]] decltype(auto) upper_bound(Rng&& rng, Value const& val, SortPredicate&& pred = {}) noexcept { static_assert( RangeReturn::allowed_if_always_has_border ); return tc_rewrap_temporary(Rng&&, RangeReturn::pack_border( iterator::upper_bound(tc::begin(tc_unwrap_temporary(rng)), tc::end(tc_unwrap_temporary(rng)), val, tc_move_if_owned(pred)), tc_unwrap_temporary(tc_move_if_owned(rng)) )); } template [[nodiscard]] decltype(auto) equal_range(Rng&& rng, Value const& val, SortPredicate&& pred = {}) noexcept { auto pairit = iterator::equal_range(tc::begin(rng), tc::end(rng), val, tc_move_if_owned(pred)); return tc::slice( tc_move_if_owned(rng), tc_move(pairit).first, tc_move(pairit).second ); } template [[nodiscard]] decltype(auto) range_in_interval_inclusive(Rng&& rng, tc::interval const& intvlval, SortPredicate&& pred = {}) noexcept { return tc_invoke(tc::chained( // clang crashes if we use return_decltype_allow_xvalue [&](auto&& rng) noexcept -> decltype(auto) { return upper_bound(tc_move_if_owned(rng), intvlval[tc::hi], tc_move_if_owned(pred)); }, [&](auto&& rng) noexcept -> decltype(auto) { return lower_bound(tc_move_if_owned(rng), intvlval[tc::lo], pred); } ), tc_move_if_owned(rng)); } template [[nodiscard]] decltype(auto) range_in_interval(Rng&& rng, tc::interval const& intvlval, SortPredicate&& pred = {}) noexcept { return tc_invoke(tc::chained( // clang crashes if we use return_decltype_allow_xvalue [&](auto&& rng) noexcept -> decltype(auto) { return lower_bound(tc_move_if_owned(rng), intvlval[tc::hi], tc_move_if_owned(pred)); }, [&](auto&& rng) noexcept -> decltype(auto) { return upper_bound(tc_move_if_owned(rng), intvlval[tc::lo], pred); } ), tc_move_if_owned(rng)); } } using namespace range; } ================================================ FILE: tc/algorithm/quantifier.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/invoke.h" #include "../range/transform_adaptor.h" #include "../variant.h" #include "for_each.h" #include "find.h" #include "compare.h" #include "any_accu.h" #include ///////////////////////////////////////////// // std::all/any/none_of on ranges namespace tc { template< typename Rng, typename Pred = tc::identity > [[nodiscard]] constexpr bool any_of(Rng&& rng, Pred&& pred = Pred()) MAYTHROW { return tc::find_first_if(tc_move_if_owned(rng), tc_move_if_owned(pred)); } template< typename Rng, typename Pred = tc::identity > [[nodiscard]] constexpr bool all_of(Rng&& rng, Pred&& pred = Pred()) MAYTHROW { return !tc::any_of(tc_move_if_owned(rng), std::not_fn(tc_move_if_owned(pred))); } // pair is in same order as if minmax_element( ..., operator<( bool, bool ) ) would have been used. template< typename Rng > [[nodiscard]] std::pair all_any_of( Rng const& rng ) MAYTHROW { std::pair pairb(true,false); tc::for_each(rng, [&](bool const b) noexcept { pairb.first=pairb.first && b; pairb.second=pairb.second || b; return continue_if( pairb.first || !pairb.second ); } ); return pairb; } template< typename Rng, typename Pred > [[nodiscard]] std::pair all_any_of(Rng const& rng, Pred&& pred) MAYTHROW { return all_any_of( tc::transform(rng,tc_move_if_owned(pred)) ); } [[nodiscard]] inline bool eager_or(std::initializer_list ab) MAYTHROW { // use initializer list instead of variadic template: initializer list guarantees evaluation in order of appearance return tc::any_of(ab); } [[nodiscard]] inline bool eager_and(std::initializer_list ab) MAYTHROW { // use initializer list instead of variadic template: initializer list guarantees evaluation in order of appearance return tc::all_of(ab); } template< typename Rng, typename Pred > [[nodiscard]] bool eager_any_of(Rng&& rng, Pred&& pred) MAYTHROW { tc::any_accu any; tc::for_each(tc::transform(rng, tc_move_if_owned(pred)), std::ref(any)); return any; } template< typename Rng, typename Pred > [[nodiscard]] bool eager_all_of(Rng&& rng, Pred&& pred) MAYTHROW { return !tc::eager_any_of(tc_move_if_owned(rng), std::not_fn(tc_move_if_owned(pred))); } // all_same_element namespace all_same_result { struct empty_t final {}; struct not_same_t final {}; template struct never_empty /*final*/ { constexpr T operator()(empty_t) const& noexcept { _ASSERTFALSE; return tc::construct_default_or_terminate(); } }; } namespace no_adl { template> struct [[nodiscard]] accumulator_all_same final { static_assert(tc::decayed); using result_type = std::variant; constexpr accumulator_all_same() noexcept = default; constexpr accumulator_all_same(auto&& equal) noexcept : m_equal(tc_move_if_owned(equal)) {} template constexpr auto operator()(U&& u) & MAYTHROW { if constexpr( std::is_same>::value ) { return tc::fn_visit( [&](all_same_result::empty_t) noexcept { return tc::continue_if(!std::holds_alternative(m_result)); }, [&](auto&& t) MAYTHROW { return operator()(tc_move_if_owned(t)); // MAYTHROW }, [&](all_same_result::not_same_t) noexcept { m_result=all_same_result::not_same_t(); return tc::break_; } )(tc_move_if_owned(u).m_result_()); } else { return tc::fn_visit( [&](all_same_result::empty_t) MAYTHROW { m_result=tc_move_if_owned(u); // MAYTHROW return tc::continue_; }, [&](T const& t) noexcept { if(m_equal(t, tc::as_const(u))) { return tc::continue_; } else { m_result=all_same_result::not_same_t(); return tc::break_; } }, [](all_same_result::not_same_t) noexcept { return tc::break_; } )(m_result); } } private: PRIVATE_MEMBER_PUBLIC_ACCESSOR(result_type, m_result) Equal m_equal; }; struct return_all_same_result; template struct all_same_element_impl; template struct all_same_element_impl final { static constexpr auto fn(Rng&& rng, Equal&& equal) MAYTHROW { accumulator_all_same, tc::decay_t> accu(tc_move_if_owned(equal)); tc::for_each(tc_move_if_owned(rng), std::ref(accu)); return accu.m_result_(); } }; template struct all_same_element_impl final { static constexpr decltype(auto) fn(Rng&& rng, Equal&& equal) MAYTHROW { // TODO: For RangeReturn return_value, without _CHECKS, the algorithm should not loop but compute the result in O(1) time. return tc::fn_visit( [](auto&& t) noexcept -> decltype(auto) { return RangeReturn::template pack_element(tc_move_if_owned(t)); }, [](tc::all_same_result::empty_t) noexcept -> decltype(auto) { return RangeReturn::template pack_no_element(); }, [](tc::all_same_result::not_same_t) noexcept -> decltype(auto) { return RangeReturn::template pack_no_element(); } )(all_same_element_impl::fn(tc_move_if_owned(rng), tc_move_if_owned(equal)) /*MAYTHROW*/); } }; } using no_adl::accumulator_all_same; using no_adl::return_all_same_result; template [[nodiscard]] constexpr decltype(auto) all_same_element(Rng&& rng, Equal&& equal = {}) MAYTHROW { return no_adl::all_same_element_impl::fn(tc_move_if_owned(rng), tc_move_if_owned(equal)); } template< typename Rng, typename Equal = tc::decay_t> [[nodiscard]] constexpr bool all_same(Rng&& rng, Equal equal = {}) noexcept { if constexpr(tc::range_with_iterators) { auto const itBegin = tc::begin(rng); auto const itEnd = tc::end(rng); if(itBegin != itEnd) { tc_auto_cref(front, *itBegin); for(auto it = itBegin; ++it != itEnd;) { if(!equal(front, *it)) return false; } } return true; } else { return !std::holds_alternative( tc::all_same_element(tc_move_if_owned(rng), tc_move(equal)) ); } } } ================================================ FILE: tc/algorithm/quantifier.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "../base/assert_defs.h" #include "../container/container.h" // tc::vector #include "../unittest.h" #include "quantifier.h" UNITTESTDEF( quantifiers ) { tc::vector v{1,2,3,4,5,6,7}; tc::vector all_even{2,4,6,8}; tc::vector all_odd{3,5,7,9}; tc_static_auto_constexpr_lambda(even) = [](int const i) noexcept { return i%2==0; }; int const existing_value = 5; int const non_existing_value = 9; auto const_range = tc::all(tc::as_const(v)); TEST_RANGE_LENGTH(const_range, 7); _ASSERT( tc::find_first(const_range, existing_value)); _ASSERT(!tc::find_first(const_range, non_existing_value)); _ASSERT(! tc::all_of(const_range, even)); _ASSERT( tc::any_of(const_range, even)); _ASSERT( tc::all_of(all_even, even)); _ASSERT( tc::any_of(all_even, even)); _ASSERT(! tc::all_of(all_odd, even)); _ASSERT(! tc::any_of(all_odd, even)); } UNITTESTDEF( all_same ) { tc::vector const v_empty{}; tc::vector const v_same{1, 1, 1, 1, 1}; tc::vector const v_different{1, 1, 2, 1, 1}; _ASSERT(tc::all_same(v_empty)); _ASSERT(!tc::all_same_element(v_empty)); _ASSERT(tc::all_same(v_same)); _ASSERT(tc::all_same_element(v_same)); _ASSERTEQUAL(tc::all_same_element(v_same), 1); _ASSERT(!tc::all_same(v_different)); _ASSERT(!tc::all_same_element(v_different)); } ================================================ FILE: tc/algorithm/restrict_size_decrement.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "size.h" namespace tc { namespace no_adl { struct[[maybe_unused]] SRestrictSizeDummy { }; #ifdef _CHECKS template< typename Rng > struct[[maybe_unused]] SRestrictSize { explicit constexpr SRestrictSize(Rng const& rng, typename boost::range_size::type nSizeMin, typename boost::range_size::type nSizeMax) noexcept : m_rng(rng) , m_nSizeMin(tc_move(nSizeMin)) , m_nSizeMax(tc_move(nSizeMax)) { } constexpr ~SRestrictSize() { auto const nSize = tc::size(m_rng); _ASSERTE(m_nSizeMin <= nSize); _ASSERTE(nSize <= m_nSizeMax); } private: Rng const& m_rng; typename boost::range_size::type m_nSizeMin; typename boost::range_size::type m_nSizeMax; }; #endif } template< typename Rng > constexpr auto restrict_size_decrement(Rng const& rng, typename boost::range_size::type nDecrementMin, typename boost::range_size::type nDecrementMax) { #ifdef _CHECKS if constexpr (tc::has_size) { auto const nSize = tc::size(rng); return no_adl::SRestrictSize( rng, nDecrementMax < nSize ? nSize - nDecrementMax : tc::explicit_cast::type>(0), nDecrementMin < nSize ? nSize - nDecrementMin : tc::explicit_cast::type>(0) ); } else #endif { return no_adl::SRestrictSizeDummy{}; } } template< typename Rng > constexpr auto restrict_size_decrement(Rng const& rng) { return restrict_size_decrement(rng, 1, 1); } } ================================================ FILE: tc/algorithm/round.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/explicit_cast.h" #include "../base/return_decltype.h" #include "../base/inplace.h" #include "../base/type_traits.h" #include "../base/integer.h" #include "compare.h" #include #include namespace tc { namespace floor_detail { template constexpr T constexpr_abs(T t) noexcept { // TODO: replace by std::abs(...) when it becomes constexpr if( 0 <= t ) { return t; } else { return -t; } } template requires (std::numeric_limits::digits/*MANT_DIG*/ <= std::numeric_limits::digits) constexpr TFloat constexpr_floor(TFloat f) noexcept { // TODO: replace by std::floor(...) when it becomes constexpr using mantint_t = typename tc::int_least_t::digits/*MANT_DIG*/>; static_assert(std::numeric_limits::lowest() < -std::numeric_limits::max()); return f != f || static_cast(std::numeric_limits::max()) <= constexpr_abs(f) ? f : static_cast(f) <= f ? static_cast(static_cast(f)) : static_cast(static_cast(f) - 1); } template requires (std::numeric_limits::digits < std::numeric_limits::digits) constexpr TFloat constexpr_floor(TFloat f) noexcept { // TODO: replace by std::floor(...) when it becomes constexpr static_assert(std::numeric_limits::lowest() < -std::numeric_limits::max()); if(f != f) { return f; } else { _ASSERT(constexpr_abs(f)<=static_cast(std::numeric_limits::max())); // depends on non-standard large integer types otherwise return static_cast(f) <= f ? static_cast(static_cast(f)) : static_cast(static_cast(f) - 1); } } } template constexpr TFloat floor(TFloat f) noexcept { // TODO: replace by std::floor(...) when it becomes constexpr if(std::is_constant_evaluated()) { return tc::floor_detail::constexpr_floor(f); } else { return std::floor(f); } } ////////////////////////////// // round template< typename T > [[nodiscard]] constexpr T round( T t ) noexcept { static_assert( std::floating_point ); return tc::floor(t+static_cast(.5)); } ////////////////////////////// // type-safe multiplication and division namespace no_adl { template struct integer_value_prefer_unsigned { using type = tc::int_value_least_t; }; template requires (0<=val) struct integer_value_prefer_unsigned { using type = tc::uint_value_least_t; }; template< typename T1, typename T2 > struct TMultiply {}; template< tc::actual_integer_like T1, tc::actual_integer_like T2 > requires std::numeric_limits::is_signed || std::numeric_limits::is_signed struct TMultiply { // For 2s complement representation, // in case of both operands signed, -(2^31) * -(2^31) = -2^62, which needs 62 bits (not including sign bit) to be represented. // In case of one unsigned and one signed, (2^32-1) * -2^31 = -2^63 + 2^31, which needs 63 bits (not including sign bit) to be represented. using type=tc::int_least_t::digits+std::numeric_limits::digits>; }; template< tc::actual_integer_like T1, tc::actual_integer_like T2 > requires (!std::numeric_limits::is_signed) && (!std::numeric_limits::is_signed) struct TMultiply { using type=tc::uint_least_t::digits+std::numeric_limits::digits>; }; template< typename T1, typename T2 > requires std::floating_point< T1 > || std::floating_point< T2 > struct TMultiply final { using type = decltype(std::declval()*std::declval()); }; template requires (1==val2) struct TMultiply> { using type = T1; }; template requires (1!=val2) struct TMultiply>: TMultiply::type> { }; template struct TMultiply, T2> final: TMultiply> {}; } template< typename Lhs, typename Rhs > [[nodiscard]] typename no_adl::TMultiply::type constexpr mul( Lhs lhs, Rhs rhs ) noexcept { return tc::explicit_cast::type>(lhs)*tc::explicit_cast::type>(rhs); } template using least_integer_constant_prefer_unsigned = tc::constant::type>(val)>; template [[nodiscard]] consteval auto mul(tc::constant lhs, tc::constant rhs) noexcept { return tc::least_integer_constant_prefer_unsigned::value, tc::least_integer_constant_prefer_unsigned::value )>::value; } template requires (0==valRhs) [[nodiscard]] constexpr auto mul(Lhs lhs, tc::constant rhs) noexcept { return tc::least_integer_constant_prefer_unsigned<0>::value; } template requires (0==valLhs) [[nodiscard]] constexpr auto mul(tc::constant lhs, Rhs rhs) noexcept { return mul(rhs, lhs); } template [[nodiscard]] constexpr auto sqr(T const& x) return_decltype_noexcept( mul(x,x) ) #ifdef _MSC_VER #pragma float_control(precise, on, push) // RT#17630: Disable compiler optimization num/den ---> num*(1/den) resulting in tc::fdiv(1287,100)==12.870000000000001 #endif [[nodiscard]] inline constexpr #ifdef __clang__ __attribute__((optnone)) // Disable compiler optimization num/den ---> num*(1/den) resulting in tc::fdiv(49,49)==0.99999999999999989 #endif double fdiv(double const num, double const den) noexcept { return num/den; } #ifdef _MSC_VER #pragma float_control(pop) #endif template< typename Num, typename Den > [[nodiscard]] constexpr double fdiv(Num num, Den den) noexcept { return tc::fdiv(static_cast(num), static_cast(den)); } struct SRoundFloor final { using is_rounding = void; template< std::integral T > constexpr T operator()( T t ) const& noexcept { return t; } template< std::floating_point T > constexpr T operator()( T t ) const& noexcept { return tc::floor(t); } template< typename T > static constexpr T PositiveOffset( T t ) noexcept { return 0; } template< typename T > static constexpr T NegativeOffset( T t ) noexcept { return t-1; } } inline constexpr roundFLOOR{}; struct SRoundNearest final { using is_rounding = void; template< std::integral T > constexpr T operator()( T t ) const& noexcept { return t; } template< std::floating_point T > constexpr T operator()( T t ) const& noexcept { return tc::round(t); } template< typename T > static constexpr T PositiveOffset( T t ) noexcept { return t/2; } template< typename T > static constexpr T NegativeOffset( T t ) noexcept { return (t-1)/2; } } inline constexpr roundNEAREST{}; struct SRoundAwayFromZero final { using is_rounding = void; template< std::integral T > constexpr T operator()( T t ) const& noexcept { return t; } template< std::floating_point T > T operator()( T t ) const& noexcept { return std::round(t); } template< typename T > static constexpr T PositiveOffset( T t ) noexcept { return t/2; } template< typename T > static constexpr T NegativeOffset( T t ) noexcept { return t/2; } } inline constexpr roundAWAYFROMZERO{}; struct SRoundCeil final { using is_rounding = void; template< std::integral T > constexpr T operator()( T t ) const& noexcept { return t; } template< std::floating_point T > T operator()( T t ) const& noexcept { return std::ceil(t); } template< typename T > static constexpr T PositiveOffset( T t ) noexcept { return t-1; } template< typename T > static constexpr T NegativeOffset( T t ) noexcept { return 0; } } inline constexpr roundCEIL{}; struct SRoundBanker final { using is_rounding = void; } inline constexpr roundBANKER{}; struct SNoRounding final { using is_rounding = void; } inline constexpr norounding{}; template concept rounding = requires { typename Round::is_rounding; }; namespace idiv_impl { // The standard guarantees that integer division rounds to zero. // [expr.mul]/4 (oder 5.6/4) template< typename Num, typename Denom, tc::rounding Round > constexpr Num idiv( Num num, Denom denom, Round round ) noexcept { static_assert( tc::actual_integer_like ); static_assert( tc::actual_integer_like< Denom > ); STATICASSERTEQUAL( std::numeric_limits::is_signed, std::numeric_limits::is_signed ); _ASSERTE( 0) { auto result = num / denom; auto remainder = num - result * denom; if(remainder<0) -tc::inplace(remainder); if(tc_auto_cref(order, tc::compare(denom,remainder * 2)); std::is_lt(order) || tc::is_eq(order) && 0 != result % 2) { result += num < 0 ? -1 : 1; } tc_return_cast(tc_move(result)); } else if constexpr(std::same_as) { _ASSERTEQUAL(num%denom, 0); tc_return_cast(num/denom); } else { tc_return_cast( ( num<0 ? num-Round::NegativeOffset(denom) : num+Round::PositiveOffset(denom) )/denom ); } } } template< typename Num, typename Denom, tc::rounding Round > [[nodiscard]] constexpr Num idiv(Num num, Denom denom, Round round) noexcept { static_assert( tc::actual_integer_like ); if constexpr( tc::actual_integer_like ) { if constexpr( std::numeric_limits::is_signed ) { if constexpr( std::numeric_limits::is_signed ) { if (denom < 0) { -tc::inplace(num); -tc::inplace(denom); } return idiv_impl::idiv(num, denom, round); } else { return tc::idiv/*not idiv_impl::idiv*/( num, tc::as_signed(denom), round ); } } else { return idiv_impl::idiv( num, tc::as_unsigned(denom), round ); } } else { static_assert( std::floating_point< Denom > ); tc_return_cast(round(num/denom)); } } template< typename Num, typename Denom, tc::rounding Round > [[nodiscard]] std::pair idivmod( Num num, Denom denom, Round round ) noexcept { Num div=tc::idiv(num,denom,round); Denom mod=num-div*denom; return std::make_pair(div,mod); } ///////////////////////////////// // scale_div template< tc::actual_integer Num, typename Denom, tc::rounding Round> [[nodiscard]] constexpr Num scale_div( Num num, Denom denom, Round round ) noexcept { return tc::idiv(num,denom,round); } template< std::floating_point Num, typename Denom> [[nodiscard]] constexpr Num scale_div( Num num, Denom denom, SRoundNearest ) noexcept { tc_return_cast(num/denom); } template< typename Num, typename Denom > [[nodiscard]] constexpr Num scale_div( Num num, Denom denom ) noexcept { return tc::scale_div(num,denom,tc::roundNEAREST); } template< bool bGeneralized, tc::actual_integer T> [[nodiscard]] constexpr T internal_lower_half(T t) noexcept { return tc::scale_div(t, 2, tc::roundFLOOR); } template< bool bGeneralized, std::floating_point T> [[nodiscard]] constexpr T internal_lower_half(T t) noexcept { return t / 2; } template< bool bGeneralized, typename Rep, typename Period> requires bGeneralized [[nodiscard]] constexpr std::chrono::duration internal_lower_half(std::chrono::duration const& dur) noexcept { return std::chrono::duration(tc::idiv(dur.count(), 2, tc::roundFLOOR)); } template< typename T > [[nodiscard]] constexpr auto lower_half(T&& t) return_decltype_noexcept( tc::internal_lower_half(tc_move_if_owned(t)) ) template< bool bGeneralized, typename T > [[nodiscard]] constexpr auto internal_midpoint(T const& begin, T const& end) noexcept { return begin + tc::internal_lower_half(end - begin); } template< typename T > [[nodiscard]] constexpr auto midpoint(T const& begin, T const& end) return_decltype_noexcept( tc::internal_midpoint(begin, end) ) ///////////////////////////////// // scale_mul template [[nodiscard]] constexpr T scale_mul(T t, Factor factor, SRoundNearest ) noexcept { tc_return_cast(tc::prepare_argument(t)*tc::prepare_argument(factor)); } template [[nodiscard]] constexpr T scale_mul(T t, Factor factor, TRound round) noexcept { tc_return_cast(round(t*factor)); } template [[nodiscard]] constexpr T scale_mul(T t, Factor factor, SRoundNearest ) noexcept { tc_return_cast(t*factor); } template [[nodiscard]] constexpr T scale_mul(T t, Factor factor) noexcept { return scale_mul(t,factor,tc::roundNEAREST); } ///////////////////////////////// // scale_muldiv namespace no_adl { template struct is_actual_integer_like_or_constant_impl final: tc::constant {}; template struct is_actual_integer_like_or_constant_impl final: tc::constant { using int_t = T; }; template struct is_actual_integer_like_or_constant_impl> final: tc::constant { using int_t = typename tc::constant::value_type; }; } template using is_actual_integer_like_or_constant = no_adl::is_actual_integer_like_or_constant_impl>; template concept actual_integer_like_or_constant = tc::is_actual_integer_like_or_constant::value; template [[nodiscard]] constexpr typename tc::is_actual_integer_like_or_constant::int_t scale_muldiv(T t, Num num, Den den, TRound round) noexcept { tc_return_cast(idiv(mul(t, num), den, round)); } template [[nodiscard]] constexpr typename tc::is_actual_integer_like_or_constant::int_t scale_muldiv(T t, Num num, Den den, TRound round) noexcept { tc_return_cast(round(mul(t, num) / den)); } template [[nodiscard]] constexpr typename tc::is_actual_integer_like_or_constant::int_t scale_muldiv(T t, Num num, Den den, TRound round) noexcept { tc_return_cast(round(t * tc::fdiv(num, den))); } template [[nodiscard]] constexpr auto scale_muldiv(T t, Num num, Den den, SRoundNearest) noexcept { return tc::reluctant_explicit_cast(t * tc::fdiv(num, den)); } template [[nodiscard]] constexpr auto scale_muldiv(T const& x, Num const& num, Den const& den) noexcept { return scale_muldiv(x, num, den, tc::roundNEAREST); } struct fraction { int m_nNum; int m_nDen; }; template [[nodiscard]] constexpr auto scale_muldiv(T const& x, tc::fraction const& fracn) noexcept { return scale_muldiv(x, fracn.m_nNum, fracn.m_nDen); } ///////////////////////////////// // rounding_cast template requires (tc::actual_integer> && (tc::actual_integer || tc::floating_point_like)) || (tc::floating_point_like> && tc::floating_point_like) [[nodiscard]] constexpr decltype(auto) rounding_cast(Src&& x, TRound) noexcept { return tc::reluctant_explicit_cast(tc_move_if_owned(x)); } template requires tc::floating_point_like> [[nodiscard]] constexpr decltype(auto) rounding_cast(Src&& x, TRound round) noexcept { return tc::reluctant_explicit_cast(round(tc_move_if_owned(x))); } template [[nodiscard]] constexpr decltype(auto) rounding_cast(Src&& x) noexcept { return rounding_cast(tc_move_if_owned(x), roundNEAREST); } template constexpr void assign_rounding_cast(Lhs& lhs, Rhs&& rhs, TRound round) noexcept { lhs=tc::rounding_cast(tc_move_if_owned(rhs), round); } template constexpr void assign_rounding_cast(Lhs& lhs, Rhs&& rhs) noexcept { lhs=tc::rounding_cast(tc_move_if_owned(rhs)); } namespace no_adl { struct fn_div { template() / std::declval())> [[nodiscard]] constexpr Quot operator()(Num&& num, Denom&& denom) const& noexcept { if constexpr( tc::actual_integer_like> && tc::actual_integer_like> ) { static_assert( dependent_false::value, "Do not rely on language int/int-behavior, use tc::scale_div " ); return tc::idiv(tc::explicit_cast(tc_move_if_owned(num)), tc_move_if_owned(denom), tc::roundNEAREST); } else { static_assert( std::floating_point> || std::floating_point> || std::is_class>::value || std::is_class>::value ); static_assert( !tc::instance, tc::size_proxy> ); static_assert( !tc::instance, tc::size_proxy> ); return tc_move_if_owned(num) / tc_move_if_owned(denom); } } }; struct fn_assign_div { template [[nodiscard]] constexpr Num& operator()(Num& num, Denom&& denom) const& noexcept { if constexpr( tc::actual_integer_like> ) { static_assert( dependent_false::value, "Do not rely on language int/int-behavior, use tc::scale_div " ); num = tc::idiv(num, tc_move_if_owned(denom), tc::roundNEAREST); } else { static_assert( std::floating_point> || std::is_class>::value ); static_assert( !tc::instance, tc::size_proxy> ); num /= tc_move_if_owned(denom); } return num; } }; } using no_adl::fn_div; using no_adl::fn_assign_div; namespace explicit_convert_adl { template [[nodiscard]] constexpr Target explicit_convert_impl(adl_tag_t, std::type_identity, Source src, Round round) noexcept { tc_return_cast(round(src)); } } } ================================================ FILE: tc/algorithm/round.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "../base/assert_defs.h" #include "../unittest.h" #include "round.h" UNITTESTDEF( rounding_cast ) { STATICASSERTSAME(decltype(tc::rounding_cast(1, tc::roundFLOOR)), int&&, "tc::rounding_cast doesn't return T"); STATICASSERTSAME(decltype(tc::rounding_cast(1.0, tc::roundFLOOR)), int, "tc::rounding_cast doesn't return T"); _ASSERTEQUAL(tc::rounding_cast(1.0, tc::roundFLOOR), 1); _ASSERTEQUAL(tc::rounding_cast(1.0, tc::roundCEIL), 1); _ASSERTEQUAL(tc::rounding_cast(1.0, tc::roundNEAREST), 1); _ASSERTEQUAL(tc::rounding_cast(1.5, tc::roundFLOOR), 1); _ASSERTEQUAL(tc::rounding_cast(1.5, tc::roundCEIL), 2); _ASSERTEQUAL(tc::rounding_cast(1.5, tc::roundNEAREST), 2); _ASSERTEQUAL(tc::rounding_cast(1, tc::roundFLOOR), 1); _ASSERTEQUAL(tc::rounding_cast(1, tc::roundCEIL), 1); _ASSERTEQUAL(tc::rounding_cast(1, tc::roundNEAREST), 1); _ASSERTEQUAL(tc::rounding_cast(1.5), 2); } STATICASSERTSAME(tc::int_value_least_t<0>, tc::int_least_t<7>); STATICASSERTSAME(tc::uint_value_least_t<0>, tc::uint_least_t<8>); STATICASSERTSAME(tc::int_value_least_t<0ull>, tc::int_least_t<7>); STATICASSERTSAME(tc::uint_value_least_t<0ll>, tc::uint_least_t<8>); STATICASSERTSAME(tc::int_value_least_t<127>, tc::int_least_t<7>); STATICASSERTSAME(tc::int_value_least_t<-128>, tc::int_least_t<7>); STATICASSERTSAME(tc::int_value_least_t<127ull>, tc::int_least_t<7>); STATICASSERTSAME(tc::int_value_least_t<-128ll>, tc::int_least_t<7>); STATICASSERTSAME(tc::uint_value_least_t<255>, tc::uint_least_t<8>); STATICASSERTSAME(tc::uint_value_least_t<255ll>, tc::uint_least_t<8>); STATICASSERTSAME(tc::int_value_least_t<128>, tc::int_least_t<15>); STATICASSERTSAME(tc::int_value_least_t<-129>, tc::int_least_t<15>); STATICASSERTSAME(tc::int_value_least_t<128ull>, tc::int_least_t<15>); STATICASSERTSAME(tc::int_value_least_t<-129ll>, tc::int_least_t<15>); STATICASSERTSAME(tc::uint_value_least_t<256>, tc::uint_least_t<16>); STATICASSERTSAME(tc::uint_value_least_t<256ll>, tc::uint_least_t<16>); STATICASSERTSAME(tc::int_value_least_t::max()>, tc::int_least_t<63>); STATICASSERTSAME(tc::int_value_least_t::lowest()>, tc::int_least_t<63>); STATICASSERTSAME(tc::uint_value_least_t::max()>, tc::uint_least_t<64>); STATICASSERTSAME(tc::uint_value_least_t::lowest()>, tc::uint_least_t<8>); namespace { template consteval auto test_mul(auto lhs, auto rhs) noexcept { auto nResult=tc::mul(lhs, rhs); STATICASSERTSAME(decltype(nResult), TExpected); return nResult; } } static_assert(15==test_mul>(tc::explicit_cast(-3), tc::explicit_cast(-5))); static_assert(-15==test_mul>(tc::explicit_cast(-3), tc::explicit_cast(5))); static_assert(15==test_mul>(tc::explicit_cast(3), tc::explicit_cast(5))); static_assert(4.5==test_mul(1.5, tc::constant<3>())); static_assert(4.5==test_mul(tc::constant<3>(), 1.5)); static_assert(5==test_mul(5, tc::constant<1>())); static_assert(5==test_mul(tc::constant<1>(), 5)); static_assert(0==test_mul>(1500, tc::constant<0>())); static_assert(0==test_mul>(tc::constant<0>(), -1500)); static_assert(15==test_mul>(tc::constant<3>(), tc::constant<5>())); static_assert(15==test_mul>(tc::constant<-3>(), tc::constant<-5>())); static_assert(-15==test_mul>(tc::constant<3>(), tc::constant<-5>())); static_assert(1500==test_mul>(tc::constant<30>(), tc::constant<50>())); static_assert(-1500==test_mul>(tc::constant<-30>(), tc::constant<50>())); static_assert(15==test_mul>(tc::explicit_cast(5), tc::constant<3>())); static_assert(15==test_mul>(tc::explicit_cast(5), tc::constant<3>())); static_assert(-15==test_mul>(tc::explicit_cast(5), tc::constant<-3>())); static_assert(15==test_mul>(tc::constant<-3>(), tc::explicit_cast(-5))); ================================================ FILE: tc/algorithm/size.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../base/explicit_cast.h" #include "../base/integer.h" #include "../base/type_traits.h" #include "../base/generic_macros.h" #include "../base/tag_type.h" #include "../base/renew.h" #include "../container/container_traits.h" #include "../range/meta.h" #include namespace tc { // forward definitions namespace size_proxy_adl { template< typename T > struct size_proxy; } using size_proxy_adl::size_proxy; template< typename T > [[nodiscard]] constexpr auto make_size_proxy(T t) noexcept; //////////////////////////////// // tc::size_proxy in ADL barrier namespace size_proxy_adl { template< typename T > struct size_proxy final { static_assert(tc::actual_integer && tc::decayed); private: constexpr void AssertInvariant() & noexcept { // npos should only be represented by a signed number. Otherwise casts to (larger) unsigned types may not preserve npos as static_cast(-1) _ASSERTE( static_cast(-1)==m_t ? std::is_signed::value : 0<=m_t ); } public: T m_t; constexpr size_proxy() noexcept : m_t() { AssertInvariant(); } constexpr explicit size_proxy(T t) noexcept : m_t(t) { AssertInvariant(); } template constexpr explicit size_proxy(size_proxy const& other) noexcept : tc_member_init( m_t, other.m_t ) { AssertInvariant(); } template< typename S > size_proxy& operator=(S&& s) & noexcept { tc::assign_explicit_cast(m_t,tc_move_if_owned(s)); AssertInvariant(); return *this; } #pragma push_macro("CONVERT") #define CONVERT( S ) \ constexpr operator S() const& noexcept { \ _ASSERTE( /*good idea to require signed?*/ std::is_signed::value && static_cast(-1)==m_t || tc::as_unsigned(m_t)<=tc::as_unsigned(std::numeric_limits::max()) ); \ return static_cast(m_t); \ } CONVERT(signed char) CONVERT(unsigned char) CONVERT(short) CONVERT(unsigned short) CONVERT(int) CONVERT(unsigned int) CONVERT(long) CONVERT(unsigned long) CONVERT(long long) CONVERT(unsigned long long) #pragma pop_macro("CONVERT") operator double() const& noexcept { _ASSERT(0 <= m_t || static_cast(-1) == m_t); tc_return_cast(m_t); } template< typename U > operator size_proxy() const& noexcept { return size_proxy(tc::implicit_cast(*this)); } #pragma push_macro("COMPARISON_OPERATOR") #define COMPARISON_OPERATOR( return_type, op ) \ template< tc::actual_integer S > \ friend constexpr return_type operator op( size_proxy const& lhs, S const& rhs ) noexcept { \ return tc::prepare_argument< T,S >(lhs.m_t) op tc::prepare_argument< T,S >(rhs); \ } \ template< typename S > \ friend constexpr return_type operator op( size_proxy const& lhs, size_proxy const& rhs ) noexcept { \ return lhs op rhs.m_t; \ } COMPARISON_OPERATOR(bool, ==) // Xcode13 doesn't compile with auto even though static_assert says the return type is bool COMPARISON_OPERATOR(auto, <=>) #pragma pop_macro("COMPARISON_OPERATOR") }; #pragma push_macro("operator_size_proxy") #define operator_size_proxy( op ) \ template< \ typename Lhs, tc::actual_integer Rhs \ > constexpr auto operator op( size_proxy const& lhs, Rhs const& rhs ) \ return_decltype_MAYTHROW( \ make_size_proxy( lhs.m_t op rhs ) \ ) \ template< \ tc::actual_arithmetic Lhs, typename Rhs \ > constexpr auto operator op( Lhs const& lhs, size_proxy const& rhs ) \ return_decltype_MAYTHROW( \ tc::explicit_cast(lhs op rhs.m_t) \ ) \ template< \ typename Lhs, typename Rhs, \ std::enable_if_t>* = nullptr \ > constexpr auto operator op( Lhs const& lhs, size_proxy const& rhs ) \ return_decltype_MAYTHROW( \ lhs op rhs.m_t \ ) \ template< typename Lhs, typename Rhs > constexpr auto operator op( size_proxy const& lhs, size_proxy const& rhs ) \ return_decltype_MAYTHROW( \ make_size_proxy( lhs.m_t op rhs.m_t ) \ ) operator_size_proxy(+) operator_size_proxy(-) operator_size_proxy(*) operator_size_proxy(/ ) operator_size_proxy(%) #pragma pop_macro("operator_size_proxy") #pragma push_macro("assign_operator_size_proxy") #define assign_operator_size_proxy( assign_op, op ) \ template< typename Lhs, typename Rhs> \ constexpr Lhs& operator assign_op ( Lhs& lhs, size_proxy const& rhs ) noexcept { \ if constexpr( std::integral ) { \ lhs=tc:: op (tc::as_const(lhs),rhs.m_t); \ } else { \ lhs assign_op rhs.m_t; \ } \ return lhs; \ } assign_operator_size_proxy(+=,add) assign_operator_size_proxy(-=,sub) #pragma pop_macro("assign_operator_size_proxy") template< tc::actual_integer Lhs, typename Rhs > Lhs& operator %= ( Lhs& lhs, size_proxy const& rhs ) noexcept { lhs%=rhs.m_t; return lhs; } } template< typename T > [[nodiscard]] constexpr auto make_size_proxy(T t) noexcept { if constexpr( tc::actual_integer ) { return size_proxy(t); } else { static_assert( tc::instance ); return t; } } template< typename T > [[nodiscard]] constexpr auto unmake_size_proxy(T t) noexcept { if constexpr( tc::instance ) { return t.m_t; } else { static_assert( tc::actual_integer ); return t; } } namespace no_adl { template using size_proxy_common_type = tc::size_proxy>; template struct common_type_decayed_impl, tc::size_proxy> : boost::mp11::mp_defer {}; template struct common_type_decayed_impl, T1> { using type = T1; }; template struct common_type_decayed_impl> { using type = T0; }; } //////////////////////////////// // tc::constexpr_size namespace no_adl { template struct constexpr_size_impl; // Rng has a size function that returns an integral_constant. template requires requires { decltype(std::declval().size())::value; } struct constexpr_size_impl : decltype(std::declval().size()) {}; template struct constexpr_size_impl : tc::least_uint_constant ? 1 : 0)> {}; template struct constexpr_size_impl> : tc::least_uint_constant {}; } template requires has_constexpr_size constexpr auto constexpr_size = []() noexcept { using type = no_adl::constexpr_size_impl>; static_assert(std::derived_from>); return tc::least_uint_constant{}; }(); //////////////////////////////// // tc::size namespace size_raw_internal { // tc::size() requires a range with either: // - a constexpr_size_impl specialization which provide size as a compile time constant // - a size member function, which is assumed to run in O(1) // - random_access iterators, which are assumed to be able to calculate size in O(1) // TODO: inline when clang supports lambdas in unevaluated contexts template requires has_constexpr_size || has_mem_fn_size || (random_access_range && tc::common_range) constexpr auto size_raw(Rng&& rng) noexcept { if constexpr( has_constexpr_size ) { return [&]() return_decltype_noexcept(constexpr_size()); } else if constexpr( has_mem_fn_size ) { return [&]() return_MAYTHROW(tc_move_if_owned(rng).size()); // .size() may throw for files } else { // Do not use boost::size. It always uses std::distance, which is O(n) for // ranges with boost::iterators::random_access_traversal_tag but not std::random_access_iterator_tag, // e.g., boost::transform_iterator return [&]() return_MAYTHROW(tc::as_unsigned(tc::end(rng) - tc::begin(rng))); } } } template [[nodiscard]] constexpr auto size_raw(Rng&& rng) return_decltype_MAYTHROW( size_raw_internal::size_raw(tc_move_if_owned(rng))() ) // Note: This overload is only necessary for the assertion - the size is otherwise computed the same by constexpr_size. template [[nodiscard]] constexpr auto size_raw(T const (&ach)[N]) noexcept { _ASSERTE(tc::strlen(ach)==N-1); // VERIFYEQUAL is not constexpr return N-1; } template [[nodiscard]] constexpr auto size(T&& t) return_decltype_MAYTHROW( make_size_proxy(tc::size_raw(tc_move_if_owned(t))) ) TC_HAS_EXPR(size, (T), size_raw(std::declval())) DEFINE_FN2(tc::size_raw, fn_size_raw) template [[nodiscard]] constexpr auto compute_range_adaptor_size(Rng&&... rng) MAYTHROW { if constexpr ((tc::has_constexpr_size && ...)) { tc::actual_unsigned_integer auto constexpr value = Fn(tc::constexpr_size()...); return tc::least_uint_constant{}; } else { tc::actual_unsigned_integer auto const value = Fn(tc::size_raw(tc_move_if_owned(rng))...); return value; } } } ================================================ FILE: tc/algorithm/size_bounded.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../container/container_traits.h" #include "size.h" #include "break_or_continue.h" #include "for_each.h" namespace tc { template< typename It, typename T, typename Sentinel > T advance_forward_bounded(It&& it, T n, Sentinel&& itBound) noexcept { _ASSERT(0 <= n); if constexpr( std::convertible_to< typename boost::iterator_traversal>::type, boost::iterators::random_access_traversal_tag > && requires { itBound - it; } ) { if (tc::assign_better(tc::fn_less_equal(), n, tc::make_size_proxy(itBound - it))) { it = tc_move_if_owned(itBound); } else { it += n; } return n; } else { // size_proxy does not provide operator++ and the operation cannot fail here, // because nCount is always inside interval [0,n]. auto nCount = tc::explicit_cast(0); while (nCount != n && it != itBound) { ++nCount; ++it; } tc_return_cast(nCount); } } template< typename Rng, typename T> [[nodiscard]] constexpr auto size_bounded(Rng const& rng, T const nBound) noexcept { if constexpr( tc::has_size ) { return tc::size(rng); } else if constexpr( tc::range_with_iterators ) { return advance_forward_bounded( tc::begin(rng), nBound, tc::end(rng) ); } else { T n = 0; if (0 < nBound) { auto const Enumerate = [&](tc::unused) noexcept { return tc::continue_if(nBound!=++n); }; STATICASSERTSAME(tc::break_or_continue, decltype(tc::for_each(rng, Enumerate)), "size_bounded only works with interruptible generators"); tc::for_each(rng, Enumerate); } return n; } } } ================================================ FILE: tc/algorithm/size_linear.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "../base/assert_defs.h" #include "../base/has_xxx.h" #include "size.h" #include "for_each.h" TC_HAS_MEM_FN_XXX_CONCEPT_DEF( size_linear, const&) namespace tc { template [[nodiscard]] constexpr auto size_linear_raw(Rng const& rng) noexcept { if constexpr( tc::has_size ) { return tc::size_raw(rng); } else if constexpr( has_mem_fn_size_linear ) { return rng.size_linear(); } else if constexpr( tc::range_with_iterators ) { #ifdef __cpp_lib_ranges // std::distance and boost::range::distance do not support end sentinels. return std::ranges::distance(tc::begin(rng), tc::end(rng)); #else typename boost::range_difference::type result = 0; auto first = tc::begin(rng); auto const last = tc::end(rng); while (first != last) { ++first; ++result; } return result; #endif } else { std::size_t sz=0; tc::for_each(rng, [&sz](auto&&...) noexcept {++sz;}); return sz; } } template [[nodiscard]] constexpr auto size_linear(T&& t) return_decltype_MAYTHROW( make_size_proxy(tc::size_linear_raw(tc_move_if_owned(t))) ) } ================================================ FILE: tc/algorithm/sort_streaming.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "append.h" #include "element.h" #include "../range/reverse_adaptor.h" #include namespace tc { template void replace_heap(Cont& cont, T&& t, Less less = Less()) noexcept { // Replace largest item in max-heap with t. // Equivalent to // std::ranges::pop_heap(cont, less); // tc::back(cont) = tc_move_if_owned(t); // std::ranges::push_heap(cont, less); auto const n = tc::size_raw(cont); auto const IndexAndIterator = [&](decltype(n) i) noexcept { return tc::make_tuple(i, tc::at(cont, i)); }; auto nitHole = IndexAndIterator(0); auto const nWithRightChildEnd = (n - 1) / 2; for( ;; ) { auto const nHole = tc::get<0>(nitHole); decltype(nitHole) nitGreaterChild; if( nHole < nWithRightChildEnd ) { // hole has right child nitGreaterChild = tc::best( tc::projected(tc::reverse_binary_rel(less), tc_fn(*tc::get<1>)), IndexAndIterator(nHole * 2 + 1), IndexAndIterator(nHole * 2 + 2) ); } else if( nHole == nWithRightChildEnd && 0 == n % 2 ) { // hole has very last left child nitGreaterChild = IndexAndIterator(nHole * 2 + 1); } else { // hole has no children break; } if( less(t, *tc::get<1>(nitGreaterChild)) ) { *tc::get<1>(nitHole) = tc_move_always(*tc::get<1>(nitGreaterChild)); nitHole = nitGreaterChild; } else { break; } } *tc::get<1>(nitHole) = tc_move_if_owned(t); } template> auto sort_streaming(Rng&& rng, Less&& less = Less(), PredKeep&& predKeep = PredKeep()) noexcept { // Notes: // * not a stable sort algorithm // * not an inplace sort algorithm // * first element is generated in O(n) return tc::generator_range_output&>([ rng = tc::make_reference_or_value(tc_move_if_owned(rng)), // std heap algorithm using less create max heap, we need min heap. Hence, we use greater. greater = tc::reverse_binary_rel(less), predKeep = tc::decay_copy(tc_move_if_owned(predKeep)) ](auto&& sink) MAYTHROW -> tc::common_type_t&>())), tc::constant> { auto vec = tc::make_vector(*rng); boost::range::make_heap(vec, greater); while( auto const it = tc::front(vec) ) { tc_return_if_break(tc::continue_if_not_break(sink, *it)) // MAYTHROW if( tc_invoke(predKeep, *it) ) { tc::replace_heap(vec, tc::range_value_t(tc_move_always(*it)), greater); } else { boost::range::pop_heap(vec, greater); tc::drop_last_inplace(vec); } } return tc::constant(); }); } } ================================================ FILE: tc/algorithm/sort_streaming.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "../base/assert_defs.h" #include "sort_streaming.h" #include "../string/ascii.h" #include "../unittest.h" UNITTESTDEF( sort_streaming ) { _ASSERTEQUAL( tc::make_str(tc::sort_streaming("5714926380")), "0123456789" ); _ASSERTEQUAL( tc::make_str( tc::transform(tc::sort_streaming("5714926380", tc::fn_greater(), tc_fn(tc::isasciidigit)), [](char& ch) noexcept { char chOrig = ch; ch -= 7; return chOrig; })), "9876543221100" ); } ================================================ FILE: tc/array.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "base/assert_defs.h" #include "base/reference_or_value.h" #include "base/type_traits.h" #include "base/explicit_cast_fwd.h" #include "base/tag_type.h" #include "base/construction_restrictiveness.h" #include "algorithm/compare.h" #include "algorithm/equal.h" #include "algorithm/empty.h" #include "algorithm/quantifier.h" #include "storage_for.h" #include "range/transform.h" #include #include #include #include namespace tc { namespace no_adl { template< typename T > struct empty_array_storage { constexpr operator T*() { return nullptr; } constexpr operator T const*() const { return nullptr; } }; template< typename T, std::size_t N > struct array_storage { using type = T[N]; }; template< typename T > struct array_storage { using type = empty_array_storage; }; } namespace array_adl { template< typename T, std::size_t N > struct array; template< typename T, std::size_t N > struct array { static_assert( !std::is_reference::value ); private: typename no_adl::array_storage::type m_a; public: using iterator = boost::indirect_iterator, /*CategoryOrTraversal*/boost::use_default, T&>; static constexpr auto size() noexcept { return tc::least_uint_constant{}; } private: template constexpr array(func_tag_t, Func func, std::index_sequence) MAYTHROW : m_a{std::addressof(func(IndexPack))...} { static_assert(tc::safely_constructible_from); } public: template array(func_tag_t, Func func) MAYTHROW : array(tc::func_tag, func, std::make_index_sequence()) {} // make sure forwarding ctor has at least two parameters, so no ambiguity with copy/move ctors template requires (tc::econstructionIMPLICIT==tc::elementwise_construction_restrictiveness::value) constexpr array(tc::aggregate_tag_t, Args& ... args) MAYTHROW : m_a{std::addressof(args)...} { STATICASSERTEQUAL(sizeof...(Args), N, "array initializer list does not match number of elements"); } private: DEFINE_NESTED_TAG_TYPE(range_tag) template constexpr array(range_tag_t, Iterator it, Iterator itEnd, std::index_sequence) MAYTHROW : m_a{(_ASSERTE(itEnd!=it), std::addressof(*it)), (static_cast(IndexPack), ++it, _ASSERTE(itEnd!=it), std::addressof(*it))...} { static_assert(tc::safely_constructible_from); STATICASSERTEQUAL(N, sizeof...(IndexPack)+1); _ASSERTE(itEnd==++it); } public: template requires (0 != N) && (tc::econstructionIMPLICIT==tc::construction_restrictiveness())))>::value) constexpr explicit array(Rng&& rng) MAYTHROW : array(range_tag, tc::begin(rng), tc::end(rng), std::make_index_sequence()) {} #if 0 template requires tc::safely_assignable_from array const& operator=(array const& rhs) const& noexcept(std::is_nothrow_assignable::value) { VERIFY(boost::copy(VERIFYINITIALIZED(rhs), begin())==end()); return *this; } template requires tc::safely_assignable_from array const& operator=(array&& rhs) const& noexcept(std::is_nothrow_assignable::value) { auto it=tc::begin(rhs); auto const itEnd=tc::end(rhs); for(auto itOut=begin(); itEnd!=it; ++it, ++itOut) { *itOut=tc_move_if_owned(VERIFYINITIALIZED(*it)); // T2 may be lvalue-reference } return *this; } #endif // iterators // reference semantics == no deep constness iterator begin() const& noexcept { return std::addressof(m_a[0]); } iterator end() const& noexcept { return std::addressof(m_a[0])+N; // std::addressof(m_a[N]); triggers warning in clang } // access (no rvalue-qualified overloads, must not move data out of a reference) // reference semantics == no deep constness [[nodiscard]] constexpr T& operator[](std::size_t const i) const& noexcept { _ASSERTDEBUG(i [[nodiscard]] constexpr bool operator==(array const& lhs, array const& rhs) noexcept { return tc::equal(VERIFYINITIALIZED(lhs), VERIFYINITIALIZED(rhs)); } template [[nodiscard]] constexpr auto operator<=>(array const& lhs, array const& rhs) noexcept { return tc::lexicographical_compare_3way(VERIFYINITIALIZED(lhs), VERIFYINITIALIZED(rhs)); } } // namespace array_adl using array_adl::array; ///////////////////////////////////////////////////// // std::array namespace no_adl { template struct constexpr_size_impl> : tc::least_uint_constant {}; } namespace explicit_convert_std_array_detail { template constexpr std::array with_func_tag_impl(std::type_identity>, Func func, std::index_sequence) noexcept(noexcept(T(func(std::declval())))) requires tc::safely_convertible_to())), T> { return {func(IndexPack)...}; } template constexpr std::array with_fill_tag_impl(std::type_identity>, std::index_sequence, auto&&... args) noexcept( noexcept(tc::explicit_cast(tc_const_forward(args)...)) && noexcept(tc::explicit_cast(tc_move_if_owned(args)...)) ) requires requires { tc::explicit_cast(tc_const_forward(args)...); } && requires { tc::explicit_cast(tc_move_if_owned(args)...); } { STATICASSERTEQUAL(N, sizeof...(IndexPack)+1); return { (tc::discard(IndexPack), tc::explicit_cast(tc_const_forward(args)...))..., tc::explicit_cast(tc_move_if_owned(args)...) }; } template constexpr std::array with_range_tag_impl(std::type_identity>, Iterator it, Iterator itEnd, Dummy&&) noexcept(noexcept(T(*it))) requires tc::safely_convertible_to { return {(_ASSERTE(itEnd != it), _ASSERTE(itEnd == tc_modified(it, ++_)), *it)}; } template requires (1 with_range_tag_impl(std::type_identity>, Iterator it, Iterator itEnd, std::index_sequence) noexcept(noexcept(T(*it)) && noexcept(++it)) requires tc::safely_convertible_to { STATICASSERTEQUAL(N, sizeof...(IndexPack)+2); return { (_ASSERTE(itEnd != it), *it), (static_cast(IndexPack), ++it, _ASSERTE(itEnd != it), *it)..., (++it, _ASSERTE(itEnd != it), _ASSERTE(itEnd == tc_modified(it, ++_)), *it) }; } namespace no_adl { template struct fn_construct_element_and_increment { // MSVC workaround: not a lambda for shorter symbol names It& m_itOut; constexpr auto operator()(auto&& t) const& noexcept(noexcept(tc::renew(*m_itOut, tc_move_if_owned(t)))) { tc::renew(*m_itOut, tc_move_if_owned(t)); // MAYTHROW ++m_itOut; } }; } } namespace explicit_convert_adl { template constexpr std::array explicit_convert_impl(adl_tag_t, std::type_identity> id, tc::func_tag_t, Func func) noexcept(noexcept(tc::explicit_convert_std_array_detail::with_func_tag_impl(id, tc_move(func), std::make_index_sequence()))) { static_assert(tc::safely_constructible_from); return tc::explicit_convert_std_array_detail::with_func_tag_impl(id, tc_move(func), std::make_index_sequence()); } template requires (0!=N) && tc::explicit_castable_from constexpr std::array explicit_convert_impl(adl_tag_t, std::type_identity> id, tc::fill_tag_t, Args&& ... args) return_MAYTHROW( tc::explicit_convert_std_array_detail::with_fill_tag_impl(id, std::make_index_sequence(), tc_move_if_owned(args)...) ) template requires (tc::explicit_castable_from && ...) constexpr std::array explicit_convert_impl(adl_tag_t, std::type_identity>, tc::aggregate_tag_t, Args&& ... args) noexcept((... && noexcept(tc::explicit_cast(tc_move_if_owned(args))))) { STATICASSERTEQUAL(sizeof...(Args), N, "array initializer list does not match number of elements"); return {tc::explicit_cast(tc_move_if_owned(args))...}; } template concept noexcept_explicit_castable = std::same_as /*optimistically assume guaranteed copy elision*/ || noexcept(tc::explicit_cast(std::declval())); template using noexcept_explicit_castable_t=tc::constant>; template constexpr std::array explicit_convert_impl(adl_tag_t, std::type_identity> id, auto&& rng) noexcept( 0==N || boost::mp11::mp_apply::template fn, tc::range_output_t>>::value ) requires (0 == N) || (econstructionEXPLICIT <= boost::mp11::mp_apply, tc::range_output_t>>::value) { if constexpr( 0 == N ) { _ASSERTE(tc::empty(rng)); return {}; } else if constexpr( std::is_trivially_default_constructible::value && std::is_trivially_destructible::value ) { std::array at; auto itOut = tc::begin(at); // cont_assign(at, transform(tc_move_if_owned(rng), tc_fn(tc::explicit_cast))); without moving rng and avoiding dependency tc::for_each(tc_move_if_owned(rng), explicit_convert_std_array_detail::no_adl::fn_construct_element_and_increment{itOut}); // MAYTHROW _ASSERTE(tc::end(at)==itOut); return at; } else if constexpr( // The initialization of the C array inside std::array when writing std::array{...} is // copy list initialization, not direct list initialization, so explicit constructors are not allowed. // int to double is considered narrowing, forbidden in list initialization (but double is already handled above) tc::safely_convertible_to ) { return tc::explicit_convert_std_array_detail::with_range_tag_impl(id, tc::begin(rng), tc::end(rng), std::make_index_sequence<1==N ? 0 : N-2>()); } else { tc_return_cast(tc::transform(tc_move_if_owned(rng), tc::fn_explicit_cast())); } } } ////////////////////////////////////////////////////////////// namespace no_adl { template struct delayed_deduce final { using type = T; }; template struct delayed_deduce final { using type = tc::common_type_t; }; } template [[nodiscard]] constexpr auto make_array(Rng&& rng) return_decltype_MAYTHROW( tc::explicit_cast>(tc_move_if_owned(rng)) ) template [[nodiscard]] constexpr auto make_array(Rng&& rng) return_decltype_MAYTHROW( tc::make_array, N>(tc_move_if_owned(rng)) ) template [[nodiscard]] constexpr auto make_array(Rng&& rng) return_decltype_MAYTHROW( tc::make_array()>(tc_move_if_owned(rng)) ) template [[nodiscard]] constexpr auto make_array(Rng&& rng) return_decltype_MAYTHROW( tc::make_array()>(tc_move_if_owned(rng)) ) template requires (!std::is_reference::value) [[nodiscard]] constexpr auto make_array(tc::aggregate_tag_t, Ts&&... ts) noexcept { static_assert(!std::is_reference::type>::value); return tc::explicit_cast::type, sizeof...(Ts)>>(tc::aggregate_tag, tc_move_if_owned(ts)...); } // If T is a reference, force argument type T for all given arguments. That way, conversions // take place in the calling expression, and cases such as // // tc::find_unique(tc::make_array(convertible_to_Foo), foo); // // will work as expected. With the usual variadic template + std::forward pattern, conversions // would take place inside the array constructor, resulting in a dangling reference. // Unfortunately, there seems to be no way to make this work in C++ without using macros #define TC_MAKE_ARRAY_LVALUE_REF(z, n, d) \ template requires std::is_lvalue_reference::value \ [[nodiscard]] constexpr auto make_array(tc::aggregate_tag_t, BOOST_PP_ENUM_PARAMS(n, T t)) noexcept { \ return tc::explicit_cast>(tc::aggregate_tag, BOOST_PP_ENUM_PARAMS(n, t)); \ } BOOST_PP_REPEAT_FROM_TO(1, 20, TC_MAKE_ARRAY_LVALUE_REF, _) #undef TC_MAKE_ARRAY_LVALUE_REF template [[nodiscard]] constexpr auto single(T&& t) noexcept { if constexpr( std::is_reference::value ) { return tc::counted(std::addressof(t),1); } else { // not tc::decay_t, we want to keep reference-like proxy objects as proxy objects // just like we preserve lvalue references. return tc::make_array >(tc::aggregate_tag,tc_move_if_owned(t)); } } } ================================================ FILE: tc/base/accessors.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include "generic_macros.h" #include "type_traits_fwd.h" #include namespace tc { // customization point accessor_return_type namespace no_adl { template struct accessor_return_type final { static_assert(tc::decayed); using const_ref_type = T; using ref_ref_type = T; using const_ref_ref_type = T; }; template requires std::is_union>::value || std::is_class>::value || std::is_array>::value struct accessor_return_type final { static_assert(std::is_array::value || tc::decayed); using const_ref_type = T const&; using ref_ref_type = T&&; using const_ref_ref_type = T const&&; }; } } #define DEFINE_MEMBER_WITHOUT_INIT(type, name) \ type name; #define DEFINE_MEMBER_WITH_INIT(type, name, ...) \ type name{__VA_ARGS__}; #define DEFINE_MEMBER_BASE(type, ...) \ TC_EXPAND(TC_EXPAND(BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), DEFINE_MEMBER_WITH_INIT, DEFINE_MEMBER_WITHOUT_INIT))(TC_FWD(type), __VA_ARGS__)) #define DEFINE_ACCESSORS_BASE(type, funcname, invariant, name) \ [[nodiscard]] constexpr typename tc::no_adl::accessor_return_type::const_ref_type funcname() const& noexcept { invariant(name); return name; } \ \ template requires /*dummy constraint making this function preferred in overload resolution over the deleted function below*/true \ [[nodiscard]] constexpr typename tc::no_adl::accessor_return_type::ref_ref_type funcname() && noexcept { invariant(name); return tc_move(name); } \ \ template /* needed to stay behind non-deleted function in overload resolution */ \ [[nodiscard]] constexpr type&& funcname() && noexcept = delete; /* Visual Studio gives improper error message if it returns a dummy type */ \ \ template requires true \ [[nodiscard]] constexpr typename tc::no_adl::accessor_return_type::const_ref_ref_type funcname() const&& noexcept { invariant(name); return tc_move_always_even_const(name); } \ \ template \ [[nodiscard]] constexpr type const&& funcname() const&& noexcept = delete; /* Visual Studio gives improper error message if it returns a dummy type */ #define DEFINE_MEMBER_INVARIANT(...) ([&](auto const& _) constexpr noexcept { \ _ASSERTINITIALIZED(_); \ __VA_ARGS__; \ }) #define INTERNAL_MEMBER_AND_NAMED_ACCESSOR_INVARIANT(accessspecifierMember, type, accessspecifierAccessor, funcname, invariant, ...) /* type, funcname, invariant, name(, value) */ \ accessspecifierMember: \ DEFINE_MEMBER_BASE(TC_FWD(type), __VA_ARGS__) \ accessspecifierAccessor: \ DEFINE_ACCESSORS_BASE(TC_FWD(type), TC_FWD(funcname), DEFINE_MEMBER_INVARIANT(invariant), BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__)) \ accessspecifierMember: /********* private member, public accessor ******************************************************************/ #define PRIVATE_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(type, funcname, invariant, ...) /* type, funcname, invariant, name(, value) */ \ INTERNAL_MEMBER_AND_NAMED_ACCESSOR_INVARIANT(private, TC_FWD(type), public, TC_FWD(funcname), TC_FWD(invariant), __VA_ARGS__) #define PRIVATE_MEMBER_PUBLIC_NAMED_ACCESSOR(type, funcname, ...) /* type, funcname, name(, value) */ \ PRIVATE_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(TC_FWD(type), TC_FWD(funcname), BOOST_PP_EMPTY(), __VA_ARGS__) #define PRIVATE_MEMBER_PUBLIC_ACCESSOR_INVARIANT(type, invariant, ...) /* type, invariant, name(, value) */ \ PRIVATE_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(TC_FWD(type), BOOST_PP_CAT(BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__), _), TC_FWD(invariant), __VA_ARGS__) #define PRIVATE_MEMBER_PUBLIC_ACCESSOR(type, ...) /* type, name(, value) */ \ PRIVATE_MEMBER_PUBLIC_ACCESSOR_INVARIANT(TC_FWD(type), BOOST_PP_EMPTY(), __VA_ARGS__) /********* protected member, public accessor ******************************************************************/ #define PROTECTED_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(type, funcname, invariant, ...) /* type, funcname, invariant, name(, value) */ \ INTERNAL_MEMBER_AND_NAMED_ACCESSOR_INVARIANT(protected, TC_FWD(type), public, TC_FWD(funcname), TC_FWD(invariant), __VA_ARGS__) #define PROTECTED_MEMBER_PUBLIC_NAMED_ACCESSOR(type, funcname, ...) /* type, funcname, name(, value) */ \ PROTECTED_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(TC_FWD(type), TC_FWD(funcname), BOOST_PP_EMPTY(), __VA_ARGS__) #define PROTECTED_MEMBER_PUBLIC_ACCESSOR_INVARIANT(type, invariant, ...) /* type, invariant, name(, value) */ \ PROTECTED_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(TC_FWD(type), BOOST_PP_CAT(BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__), _), TC_FWD(invariant), __VA_ARGS__) #define PROTECTED_MEMBER_PUBLIC_ACCESSOR(type, ...) /* type, name(, value) */ \ PROTECTED_MEMBER_PUBLIC_ACCESSOR_INVARIANT(TC_FWD(type), BOOST_PP_EMPTY(), __VA_ARGS__) /********* public member, public accessor ******************************************************************/ #define PUBLIC_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(type, funcname, invariant, ...) /* type, funcname, invariant, name(, value) */ \ INTERNAL_MEMBER_AND_NAMED_ACCESSOR_INVARIANT(public, TC_FWD(type), public, TC_FWD(funcname), TC_FWD(invariant), __VA_ARGS__) #define PUBLIC_MEMBER_PUBLIC_NAMED_ACCESSOR(type, funcname, ...) /* type, funcname, name(, value) */ \ PUBLIC_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(TC_FWD(type), TC_FWD(funcname), BOOST_PP_EMPTY(), __VA_ARGS__) #define PUBLIC_MEMBER_PUBLIC_ACCESSOR_INVARIANT(type, invariant, ...) /* type, invariant, name(, value) */ \ PUBLIC_MEMBER_PUBLIC_NAMED_ACCESSOR_INVARIANT(TC_FWD(type), BOOST_PP_CAT(BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__), _), TC_FWD(invariant), __VA_ARGS__) #define PUBLIC_MEMBER_PUBLIC_ACCESSOR(type, ...) /* type, name(, value) */ \ PUBLIC_MEMBER_PUBLIC_ACCESSOR_INVARIANT(TC_FWD(type), BOOST_PP_EMPTY(), __VA_ARGS__) /********* private member, private accessor ******************************************************************/ #define PRIVATE_MEMBER_PRIVATE_ACCESSOR_INVARIANT(type, invariant, ...) /* type, invariant, name(, value) */ \ INTERNAL_MEMBER_AND_NAMED_ACCESSOR_INVARIANT(private, TC_FWD(type), private, BOOST_PP_CAT(BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__), _), TC_FWD(invariant), __VA_ARGS__) ================================================ FILE: tc/base/as_lvalue.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once namespace tc { template [[nodiscard]] constexpr T& as_lvalue(T&& t) noexcept { return static_cast(t); // required as soon as "P2266R3: Simpler implicit move" is implemented } } ================================================ FILE: tc/base/assert_defs.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #ifdef TC_PRIVATE #include "Library/ErrorReporting/assert_fwd.h" #else #include "fundamental.h" #include "move.h" #include #include #ifndef _CHECKS #define _CHECKS #endif #ifndef IF_TC_CHECKS #ifdef _CHECKS #define IF_TC_CHECKS(...) __VA_ARGS__ #else #define IF_TC_CHECKS(...) #endif #endif #ifndef IF_TC_DEBUG #ifdef _DEBUG #define IF_TC_DEBUG(...) __VA_ARGS__ #else #define IF_TC_DEBUG(...) #endif #endif #ifndef _ASSERT #ifdef NDEBUG #define _ASSERT(...) IF_TC_CHECKS((TC_FWD(__VA_ARGS__) ? (void)0 : std::abort())) #else #include #define _ASSERT(...) IF_TC_CHECKS(assert(TC_FWD(__VA_ARGS__))) #endif #endif #ifndef TRYASSERT #define TRYASSERT _ASSERT #endif #ifndef _ASSERTE #define _ASSERTE(...) (static_cast(0)) #endif #ifndef _ASSERTDEBUG #define _ASSERTDEBUG(...) IF_TC_DEBUG(_ASSERT((__VA_ARGS__))) #endif #ifndef _ASSERTFALSE #define _ASSERTFALSE _ASSERT(false) #endif #ifndef _ASSERTNORETURN #define _ASSERTNORETURN _ASSERT #endif #ifndef _ASSERTNORETURNFALSE #define _ASSERTNORETURNFALSE _ASSERTFALSE #endif #ifndef _ASSERTNOTIFY #define _ASSERTNOTIFY _ASSERT #endif #ifndef _ASSERTNOTIFYFALSE #define _ASSERTNOTIFYFALSE _ASSERTFALSE #endif #ifndef _ASSERTENOTIFY #define _ASSERTENOTIFY _ASSERTE #endif #ifndef _ASSERTEQUAL #define _ASSERTEQUAL(a, b) _ASSERT((a)==(b)) #endif #ifndef _ASSERTDEBUGEQUAL #define _ASSERTDEBUGEQUAL(a, b) IF_TC_DEBUG(_ASSERTEQUAL(a, b)) #endif #ifndef _ASSERTANYOF #include #define _ASSERTANYOF(expr, values) [](auto const& e, auto const&... val) noexcept { _ASSERT( ((e == val) || ...) ); }(expr, BOOST_PP_SEQ_ENUM(values)) #endif #ifndef _ASSERTDEBUGANYOF #define _ASSERTDEBUGANYOF(expr, values) IF_TC_DEBUG(_ASSERTANYOF(expr, values)) #endif #ifndef _ASSERTINITIALIZED #define _ASSERTINITIALIZED( expr ) tc::discard(expr) #endif #ifndef _ASSERTPRINT #define _ASSERTPRINT( cond, ... ) _ASSERT( cond ) #endif #ifndef VERIFYEQUAL namespace ErrorHandling { template constexpr Expr&& VerifyEqual(Expr&& expr, Const const& c) { _ASSERTEQUAL(expr, c); return tc_move_if_owned(expr); } } #define VERIFYEQUAL( expr, constant ) ErrorHandling::VerifyEqual(expr, constant) #endif #ifndef VERIFYEQUALNOPRINT #define VERIFYEQUALNOPRINT VERIFYEQUAL #endif #ifndef VERIFY namespace ErrorHandling { template constexpr Expr&& Verify(Expr&& expr) { _ASSERT(expr); return tc_move_if_owned(expr); } } #define VERIFY ErrorHandling::Verify #endif #ifndef VERIFYPRED namespace ErrorHandling { template constexpr Expr&& VerifyPred(Expr&& expr, Pred pred) { _ASSERT(pred(expr)); return tc_move_if_owned(expr); } } #define VERIFYPRED( expr, ... ) ErrorHandling::VerifyPred(expr, [&](auto const& _) { return (__VA_ARGS__); }) #endif #ifndef VERIFYINITIALIZED #define VERIFYINITIALIZED( expr ) (expr) #endif #ifndef VERIFYNOTIFYEQUAL #define VERIFYNOTIFYEQUAL VERIFYEQUAL #endif #ifndef VERIFYNOTIFY #define VERIFYNOTIFY VERIFY #endif #ifndef VERIFYNORETURN #define VERIFYNORETURN VERIFY #endif #ifndef VERIFYCRITICALPRED #define VERIFYCRITICALPRED VERIFYPRED #endif #ifndef VERIFYNOTIFYPRED #define VERIFYNOTIFYPRED VERIFYPRED #endif #ifndef NOBADALLOC #define NOBADALLOC( expr ) (expr) #endif #ifndef NOEXCEPT #define NOEXCEPT( ... ) \ [&]() noexcept -> decltype(auto) { \ return (__VA_ARGS__); \ }() #endif #ifndef NOEXCEPT_NO_LAMBDA #define NOEXCEPT_NO_LAMBDA( ... ) (__VA_ARGS__) #endif #define switch_no_default(...) \ switch( auto const& /*lifetime extended until end of switch block*/ __switch=(__VA_ARGS__) ) \ default: \ if ( _ASSERTFALSE, false ) {std::abort(); /*never executed, but the compiler might complain about this code path not returning a value*/} \ else namespace tc { template T construct_default_or_terminate() noexcept { if constexpr( !std::is_void::value ) { if constexpr( std::is_default_constructible::value ) { try { return T(); } catch ( ... ) {} } std::abort(); // same behavior as violated noexcept } } } #endif ================================================ FILE: tc/base/bit_cast.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "casts.h" #include "../range/subrange.h" #include "../array.h" #include //----------------------------------------------------------------------------------------------------------------------------- namespace tc { //-------------------------------------------------------------------------------------------------------------------------- // as_blob // reinterprets a range of items as a range of bytes // We use unsigned char for uninterpreted memory. // - The type used must support aliasing, so the only candidates are char, signed char and unsigned char. // - char is bad because we interpret char as UTF-8 and char* as zero-terminated range. // - unsigned char is better than signed char because the binary representation of signs may vary between platforms. // - char is bad because it is either signed or unsigned, so it has the same problem. // - unsigned char is better than std::uint8_t because the latter must be 8 bit, but we mean the smallest addressable unit, which is char and may be larger (or smaller?) on other platforms. static_assert(!tc::range_with_iterators< void const* >); namespace range_as_blob_detail { // not static local lambda to workaround MSVC bugs: 1. static local instantiation. 2. reference to constexpr auto as_blob_ptr(auto ptr) noexcept { return reinterpret_cast>*>(ptr); }; } template [[nodiscard]] auto range_as_blob(Rng&& rng) noexcept { using cv_value_type = std::remove_pointer_t; static_assert( std::is_trivially_copyable< cv_value_type >::value, "as_blob only works on std::is_trivially_copyable types" ); if constexpr(tc::safely_constructible_from, Rng>) { return tc::make_iterator_range( reinterpret_cast*>(tc::ptr_begin(rng)), reinterpret_cast*>(tc::ptr_end(rng)) ); } else { struct blob_range_t final { explicit blob_range_t(Rng&& rng) noexcept : m_rng(tc_move(rng)) {} auto begin() & noexcept { return range_as_blob_detail::as_blob_ptr(tc::ptr_begin(m_rng)); } auto begin() const& noexcept { return range_as_blob_detail::as_blob_ptr(tc::ptr_begin(m_rng)); } auto end() & noexcept { return range_as_blob_detail::as_blob_ptr(tc::ptr_end(m_rng)); } auto end() const& noexcept { return range_as_blob_detail::as_blob_ptr(tc::ptr_end(m_rng)); } private: static_assert(!std::is_reference::value); std::remove_cv_t m_rng; }; return blob_range_t(tc_move(rng)); } } namespace no_adl { template struct range_as_blob_sink { // no final: verify_sink_result_impl derives private: // range_as_blob_sink is only used inline in range_as_blob below, and m_sink is only passed to tc::for_each, so holding by lvalue reference ok Sink& m_sink; public: explicit range_as_blob_sink(Sink& sink) noexcept: m_sink(sink) {} // chunk must be defined before operator() - otherwise MSVC will not allow it to occur in the return type of operator() template auto chunk(Rng&& rng) const& return_decltype_MAYTHROW ( tc::for_each(tc::range_as_blob(tc_move_if_owned(rng)), m_sink) ) template auto operator()(T&& t) const& return_decltype_MAYTHROW ( chunk(tc::single(/* no tc_move_if_owned */ t)) ) }; } template [[nodiscard]] auto range_as_blob(Rng&& rng) noexcept { return [rng=tc::make_reference_or_value(tc_move_if_owned(rng))](auto&& sink) MAYTHROW { return tc::for_each(*rng, no_adl::range_as_blob_sink>(sink)); }; } template requires std::is_trivially_copyable>::value [[nodiscard]] auto as_blob(T&& t) noexcept { return tc::range_as_blob( tc::single( tc_move_if_owned(t) ) ); } namespace assert_no_overlap_impl { void assert_no_overlap(auto const& lhs, auto const& rhs) noexcept { _ASSERT( reinterpret_cast(tc::ptr_end(lhs)) <= reinterpret_cast(tc::ptr_begin(rhs)) || reinterpret_cast(tc::ptr_end(rhs)) <= reinterpret_cast(tc::ptr_begin(lhs)) ); } } template< typename Lhs, typename Rhs> void assert_no_overlap(Lhs const& lhs, Rhs const& rhs) noexcept { assert_no_overlap_impl::assert_no_overlap(tc::single(lhs), tc::single(rhs)); if constexpr( tc::contiguous_range && tc::contiguous_range ) { assert_no_overlap_impl::assert_no_overlap(lhs, rhs); } } ///////////////////////////////////////////// // bit_cast namespace no_adl { struct any_ptr_ref final: tc::nonmovable { private: void* m_pv; public: explicit any_ptr_ref(void* pv) noexcept: m_pv(pv) {} template requires std::same_as, T> && std::is_trivially_copyable::value operator T() && noexcept { T t; std::memcpy(std::addressof(t), m_pv, sizeof(t)); return t; } }; } namespace any_ptr_adl { struct any_ptr final { private: void* m_pv; public: any_ptr( void* pv ) noexcept : m_pv(pv) {} explicit operator bool() const& noexcept { return m_pv; } auto operator*() const& noexcept { return tc::no_adl::any_ptr_ref(m_pv); } template requires std::is_pointer::value || std::is_member_pointer::value operator T() const& noexcept { STATICASSERTEQUAL(sizeof(T), sizeof(void*)); T t; std::memcpy( std::addressof(t), std::addressof(m_pv), sizeof(t) ); // bit_cast to allow cast to member function pointers return t; } }; } using any_ptr_adl::any_ptr; namespace no_adl { template struct type { private: same_cvref_t* m_pb; public: explicit type(same_cvref_t* pb) noexcept : m_pb(pb) {} explicit type(tc::any_ptr p) noexcept : m_pb(p) {} operator std::remove_cv_t() const& noexcept { std::remove_cv_t t; std::memcpy( std::addressof(t), m_pb, sizeof(t) ); return t; } type const& operator=( std::remove_cv_t const& rhs ) const& noexcept { boost::copy( tc::as_blob(rhs), m_pb ); return *this; } }; template struct decay, bPreventSlicing> { using type=typename tc::decay::type; // recursive }; } template struct aliasing_ref final { static_assert( !std::is_reference::value ); static_assert( std::is_trivially_copyable::value ); using type=no_adl::type; static type construct(same_cvref_t* pb) noexcept { return type(pb); } }; template struct aliasing_ref final { static_assert( !std::is_reference::value ); static_assert( std::is_trivially_copyable::value ); using type = std::remove_cv_t; static type construct(same_cvref_t* pb) noexcept { type t; std::memcpy( std::addressof(t), pb, sizeof(t) ); return t; } }; template< typename T> struct aliasing_ptr final { static_assert( !std::is_reference::value ); static_assert( std::is_trivially_copyable::value ); struct type { private: same_cvref_t* m_pb; public: type() noexcept {} explicit type(T* pt) noexcept : m_pb(reinterpret_cast*>(pt)) {} explicit operator bool() const& noexcept { return m_pb; } type& operator=(std::nullptr_t) & noexcept { m_pb=nullptr; return *this; } typename aliasing_ref::type operator*() const& noexcept { return aliasing_ref::construct(m_pb); } type& operator+=( std::ptrdiff_t n ) & noexcept { m_pb+=n*static_cast(sizeof(T)); // cast to signed ptrdiff_t to silence UB sanitizer return *this; } type& operator-=( std::ptrdiff_t n ) & noexcept { return *this+=-n; } template< typename Offset > friend type operator+( type ptr, Offset n ) noexcept { return ptr+=n; } template< typename Offset > friend type operator-( type ptr, Offset n ) noexcept { return ptr-=n; } typename aliasing_ref::type operator[]( std::ptrdiff_t n ) const& noexcept { return *(*this+n); } }; }; template< typename T > requires std::is_function::value struct aliasing_ptr final { using type = T*; }; template<> struct aliasing_ptr final { using type = char*; }; template<> struct aliasing_ptr final { using type = char const*; }; template<> struct aliasing_ptr final { using type = unsigned char*; }; template<> struct aliasing_ptr final { using type = unsigned char const*; }; template [[nodiscard]] Dst bit_cast_range(Src const& src) noexcept { tc_auto_cref(rng, tc::range_as_blob(src)); STATICASSERTSAME(std::remove_cvref_t, Dst); _ASSERTEQUAL(tc::size(rng), sizeof(Dst)); static_assert(std::is_trivially_copyable< Dst >::value); Dst dst; std::memcpy(std::addressof(dst), tc::ptr_begin(rng), sizeof(dst)); return dst; } // no danger of aliasing template [[nodiscard]] constexpr Dst bit_cast(Src const& src) noexcept { static_assert( !std::is_pointer::value || !std::is_pointer::value ); STATICASSERTSAME(std::remove_cvref_t, Dst ); STATICASSERTEQUAL(sizeof(Dst), sizeof(Src), "bit_cast source and destination must be same size"); static_assert(std::is_trivially_copyable::value && std::is_trivially_copyable::value); // Visual Studio use __builtin_bit_cast to implement std::bit_cast. // clang has __builtin_bit_cast but does not support std::bit_cast (Xcode 13). return __builtin_bit_cast(Dst, src); } // danger of aliasing template [[nodiscard]] typename aliasing_ptr>::type aliasing_cast(Src const& src) noexcept { static_assert( std::is_pointer::value ); static_assert( std::is_pointer::value ); STATICASSERTSAME(std::remove_cvref_t, Dst); return typename aliasing_ptr>::type(reinterpret_cast(src)); } } ================================================ FILE: tc/base/bitfield.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include #include #if defined(_MSC_VER) && defined(_M_ARM64EC) #include #endif namespace tc { template [[nodiscard]] constexpr int index_of_least_significant_bit(Uint u) noexcept { return std::countr_zero(VERIFYPRED(u, 0 != _)); } template [[nodiscard]] constexpr Uint least_significant_bit(Uint u) noexcept { static_assert( std::is_unsigned::value ); MODIFY_WARNINGS_BEGIN(((disable)(4146))) // unary minus operator applied to unsigned type, result still unsigned return u & -u; MODIFY_WARNINGS_END } template [[nodiscard]] constexpr int index_of_most_significant_bit(Uint u) noexcept { // return std::bit_width() - 1; return std::numeric_limits::digits - 1 - std::countl_zero(VERIFYPRED(u, 0 != _)); } template [[nodiscard]] constexpr Uint most_significant_bit(Uint u) noexcept { // return std::bit_floor(u); if( 0 == u ) { return 0; } else { return Uint{1} << tc::index_of_most_significant_bit(u); } } } ================================================ FILE: tc/base/bitfield.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "assert_defs.h" #include "bitfield.h" #include "../unittest.h" namespace { auto constexpr nAllBitsSet = static_cast(-1); } STATICASSERTEQUAL( tc::index_of_least_significant_bit(nAllBitsSet), 0 ); STATICASSERTEQUAL( tc::index_of_least_significant_bit(0x00000001u), 0 ); STATICASSERTEQUAL( tc::index_of_least_significant_bit(0x00018000u), 15 ); STATICASSERTEQUAL( tc::index_of_least_significant_bit(0x000ff000u), 12 ); STATICASSERTEQUAL( tc::index_of_least_significant_bit(0x80000000u), 31 ); STATICASSERTEQUAL( tc::index_of_least_significant_bit(0x80000001u), 0 ); STATICASSERTEQUAL( tc::least_significant_bit(0u), 0u ); STATICASSERTEQUAL( tc::least_significant_bit(nAllBitsSet), 1u ); STATICASSERTEQUAL( tc::least_significant_bit(0x00000001u), 0x00000001u ); STATICASSERTEQUAL( tc::least_significant_bit(0x00018000u), 0x00008000u ); STATICASSERTEQUAL( tc::least_significant_bit(0x000ff000u), 0x00001000u ); STATICASSERTEQUAL( tc::least_significant_bit(0x80000000u), 0x80000000u ); STATICASSERTEQUAL( tc::least_significant_bit(0x80000001u), 0x00000001u ); STATICASSERTEQUAL( tc::index_of_most_significant_bit(nAllBitsSet), std::numeric_limits::digits - 1 ); STATICASSERTEQUAL( tc::index_of_most_significant_bit(0x00000001u), 0 ); STATICASSERTEQUAL( tc::index_of_most_significant_bit(0x00018000u), 16 ); STATICASSERTEQUAL( tc::index_of_most_significant_bit(0x000ff000u), 19 ); STATICASSERTEQUAL( tc::index_of_most_significant_bit(0x80000000u), 31 ); STATICASSERTEQUAL( tc::index_of_most_significant_bit(0x80000001u), 31 ); STATICASSERTEQUAL( tc::most_significant_bit(0u), 0u ); STATICASSERTEQUAL( tc::most_significant_bit(nAllBitsSet), 1u << ( sizeof(nAllBitsSet)*CHAR_BIT-1 ) ); STATICASSERTEQUAL( tc::most_significant_bit(0x00000001u), 0x00000001u ); STATICASSERTEQUAL( tc::most_significant_bit(0x00018000u), 0x00010000u ); STATICASSERTEQUAL( tc::most_significant_bit(0x000ff000u), 0x00080000u ); STATICASSERTEQUAL( tc::most_significant_bit(0x80000000u), 0x80000000u ); STATICASSERTEQUAL( tc::most_significant_bit(0x80000001u), 0x80000000u ); ================================================ FILE: tc/base/casts.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include "functors.h" #include "explicit_cast_fwd.h" #include #ifndef __EMSCRIPTEN__ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // sprintf is deprecated in Xcode14.1 RC #endif #include #ifdef __clang__ #pragma clang diagnostic pop #endif #endif #include //----------------------------------------------------------------------------------------------------------------------------- namespace tc { //------------------------------------------------------------------------------------------------------------------------- ///////////////////////////////////////////// // base_cast // (cannot be implemented like derived_cast because when deriving protected, derived to base cast is often publicly inaccessible) #pragma push_macro("BASE_CAST_IMPL") #define BASE_CAST_IMPL(cvref) \ template requires std::is_class::value \ [[nodiscard]] constexpr Dst cvref base_cast(typename std::type_identity::type cvref t) noexcept { \ STATICASSERTSAME(std::remove_cvref_t, Dst); \ return static_cast(t); \ } BASE_CAST_IMPL(&) BASE_CAST_IMPL(&&) BASE_CAST_IMPL(*) BASE_CAST_IMPL(const&) BASE_CAST_IMPL(const&&) BASE_CAST_IMPL(const*) BASE_CAST_IMPL(volatile&) BASE_CAST_IMPL(volatile&&) BASE_CAST_IMPL(volatile*) BASE_CAST_IMPL(volatile const&) BASE_CAST_IMPL(volatile const&&) BASE_CAST_IMPL(volatile const*) #pragma pop_macro("BASE_CAST_IMPL") ///////////////////////////////////////////// // derived_cast namespace derived_cast_detail { template constexpr void check_derived_cast(From const& obj) noexcept { if constexpr (!std::is_same::value && std::is_polymorphic::value) { _ASSERT(dynamic_cast(std::addressof(obj))); } } namespace derived_cast_internal_default { template [[nodiscard]] constexpr same_cvref_t< To, From&&> derived_cast_internal_impl(std::type_identity, From&& t, std::bool_constant) noexcept { static_assert( tc::derived_from>, "derived_cast is for downcasts only."); if constexpr (bChecked) { check_derived_cast(t); } return static_cast< apply_cvref_t< To, From&&> >(t); } template [[nodiscard]] constexpr same_cvref_t< To, From>* derived_cast_internal_impl(std::type_identity, From* pt, std::bool_constant) noexcept { static_assert( tc::derived_from>, "derived_cast is for downcasts only."); if constexpr (bChecked) { if (nullptr != pt) check_derived_cast(*pt); } return static_cast< apply_cvref_t< To, From>* >(pt); } } DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(derived_cast_internal) } template [[nodiscard]] constexpr decltype(auto) derived_cast(From&& t) noexcept { STATICASSERTSAME(std::remove_reference_t, To); return tc::derived_cast_detail::derived_cast_internal(std::type_identity(), tc_move_if_owned(t), /*bChecked*/tc::constant()); } template [[nodiscard]] constexpr decltype(auto) unchecked_derived_cast(From&& t) noexcept { STATICASSERTSAME(std::remove_reference_t, To); return tc::derived_cast_detail::derived_cast_internal(std::type_identity(), tc_move_if_owned(t), /*bChecked*/tc::constant()); } ///////////////////////////////////////////// // to_underlying namespace to_underlying_default { template< tc::char_type T > [[nodiscard]] constexpr auto to_underlying_impl(T t) return_decltype_noexcept( static_cast::exact>(t) ) template< tc::enum_type T > [[nodiscard]] constexpr auto to_underlying_impl( T e ) noexcept { return static_cast>(e); } // No implicit conversions to bool template< std::same_as T > [[nodiscard]] constexpr auto to_underlying_impl(T b) noexcept { STATICASSERTEQUAL(sizeof(bool), sizeof(unsigned char)); return static_cast(b); } } DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(to_underlying) template using underlying_type_t = decltype(tc::to_underlying(std::declval())); namespace from_underlying_detail { namespace from_underlying_default { template< tc::char_type T > [[nodiscard]] constexpr auto from_underlying_impl(std::type_identity, underlying_type_t t) noexcept { // We don't need to do any checks, the underlying type has the same size. return static_cast(t); } template< tc::enum_type T > [[nodiscard]] constexpr auto from_underlying_impl(std::type_identity, underlying_type_t e) noexcept { // We cannot do checks on arbitrary enums; there's a specialization for contiguous enums. return static_cast(e); } [[nodiscard]] constexpr auto from_underlying_impl(std::type_identity, underlying_type_t b) noexcept { _ASSERTANYOF(b, (0)(1)); return static_cast(b); } } DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(from_underlying) } template requires tc::explicit_castable_from, U> [[nodiscard]] constexpr T from_underlying(const U& value) return_MAYTHROW( from_underlying_detail::from_underlying(std::type_identity{}, tc::explicit_cast>(value)) ) ///////////////////////////////////////////// // as_unsigned/signed template< typename T > [[nodiscard]] constexpr auto as_unsigned(T t) noexcept code_return_decltype( static_assert( tc::actual_integer ); _ASSERTE( 0<=t );, static_cast>(t) ) template< typename T > [[nodiscard]] constexpr std::make_signed_t as_signed(T t) noexcept { static_assert( tc::actual_integer ); if constexpr (!std::is_signed::value) { _ASSERTE(t <= tc::as_unsigned(std::numeric_limits>::max())); } return static_cast>(t); } #define tc_decay_bitfield(...) \ ([&](auto char_const_volatile) noexcept { \ using BitfieldBaseType = std::remove_reference_t; \ static_assert( \ (tc::actual_integer || tc::enum_type) && \ /* Taking the address of a bit-field is disallowed and a non-const reference cannot bind to a bit-field. */ \ !requires { reinterpret_cast(__VA_ARGS__); }, \ "\"" #__VA_ARGS__ "\" is not a bit field" \ ); \ return __VA_ARGS__; \ })(std::type_identity{}) ///////////////////////////////////////////// // const casts MODIFY_WARNINGS_BEGIN(((disable)(4180))) // qualifier applied to function type has no meaning; ignored template [[nodiscard]] constexpr T const& as_const(T& t) noexcept { // intention is to avoid side-effects return static_cast(t); } template [[nodiscard]] constexpr T const& as_const(tc::temporary& tmp) noexcept { return static_cast(tmp); } template requires std::is_rvalue_reference::value [[nodiscard]] constexpr T&& as_const(T&& t) noexcept { // needed in generic code when both values and references can occur static_assert(!std::is_lvalue_reference::value); return static_cast(t); } template< typename T > [[nodiscard]] constexpr std::remove_const_t& as_mutable(T& t) noexcept { return const_cast&>(t); } MODIFY_WARNINGS_END template< typename T > [[nodiscard]] constexpr T const* as_const_ptr( T const* pt ) noexcept { return pt; } template< typename T > [[nodiscard]] constexpr T* as_mutable_ptr( T const* pt ) noexcept { return const_cast(pt); } ///////////////////////////////////////////// // void_cast template [[nodiscard]] Dst* void_cast(Src* p) noexcept{ static_assert(std::is_void::value,"Src must be possibly qualified void*"); static_assert(!std::is_reference::value); // static_assert(!std::is_void::value); // practical for generic code to allow it return static_cast(p); } template [[nodiscard]] Dst const* void_cast(Src const* p) noexcept{ static_assert(std::is_void::value,"Src must be possibly qualified void*"); static_assert(!std::is_reference::value); // static_assert(!std::is_void::value); // practical for generic code to allow it return static_cast(p); } template [[nodiscard]] Dst volatile* void_cast(Src volatile* p) noexcept{ static_assert(std::is_void::value,"Src must be possibly qualified void*"); static_assert(!std::is_reference::value); // static_assert(!std::is_void::value); // practical for generic code to allow it return static_cast(p); } template [[nodiscard]] Dst volatile const* void_cast(Src volatile const* p) noexcept{ static_assert(std::is_void::value,"Src must be possibly qualified void*"); static_assert(!std::is_reference::value); // static_assert(!std::is_void::value); // practical for generic code to allow it return static_cast(p); } ///////////////////////////////////////////// // implicit_cast template requires (!tc::actual_integer>) && tc::safely_convertible_to [[nodiscard]] constexpr TTarget implicit_cast(TSource&& src) noexcept { return tc_move_if_owned(src); } // bit filed cannot bind to universal reference MODIFY_WARNINGS_BEGIN(((disable)(4244))) // disable warning C4244: conversion from 'int' to 'float', possible loss of data template requires tc::actual_integer && tc::safely_convertible_to [[nodiscard]] constexpr TTarget implicit_cast(TSource src) noexcept { return src; } MODIFY_WARNINGS_END ///////////////////////////////////////////// // reluctant_implicit_cast // Returns a reference to its argument whenever possible, otherwise performs an implicit conversion. template [[nodiscard]] std::conditional_t< tc::decayed_derived_from, TSource&&, TTarget > reluctant_implicit_cast(TSource&& src) noexcept { STATICASSERTSAME(std::remove_cvref_t, TTarget); return tc_move_if_owned(src); } ///////////////////////////////////////////// // as_c_str namespace as_c_str_default { template< typename Char, typename Traits, typename Alloc > [[nodiscard]] Char const* as_c_str_impl(std::basic_string< Char, Traits, Alloc > const& str) noexcept { return str.data(); // since C++ 11, performs same function as c_str(). cannot use tc::ptr_begin to avoid circular dependency } template< typename Char, typename Traits, typename Alloc > [[nodiscard]] Char* as_c_str_impl(std::basic_string< Char, Traits, Alloc >& str) noexcept { return str.data(); // since C++ 11, performs same function as c_str(). cannot use tc::ptr_begin to avoid circular dependency } template< typename Char, typename Traits, typename Alloc > [[nodiscard]] Char* as_c_str_impl(std::basic_string< Char, Traits, Alloc >&& str) noexcept { return str.data(); // since C++ 11, performs same function as c_str(). cannot use tc::ptr_begin to avoid circular dependency } template [[nodiscard]] constexpr Char const* as_c_str_impl(Char const* psz) noexcept { return psz; } template [[nodiscard]] constexpr Char* as_c_str_impl(Char* psz) noexcept { return psz; } #ifdef TC_PRIVATE // Prevents implicit conversion from std::vector to boost::filesystem::path on Windows, which causes a dangling pointer to be returned template requires std::is_same::value [[nodiscard]] inline boost::filesystem::path::value_type const* as_c_str_impl(T const& fspath) noexcept { return fspath.c_str(); } #endif } DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(as_c_str) } ================================================ FILE: tc/base/casts.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "assert_defs.h" #include "casts.h" #include "../unittest.h" #include namespace { [[maybe_unused]] void static_tests_as_const() { int value; tc::temporary tmp(value); STATICASSERTSAME(decltype(tc::as_const(value)), int const&); STATICASSERTSAME(decltype(tc::as_const(tmp)), int const&); STATICASSERTSAME(decltype(tc::as_const(0)), int&&); STATICASSERTSAME(decltype(tc::as_const(tc::temporary(0))), TC_FWD(tc::temporary&&)); } } namespace { struct base{}; struct derived final : base{}; } UNITTESTDEF(DerivedCastTests) { base b; derived& d = tc::derived_cast(b); _ASSERTEQUAL(std::addressof(b), std::addressof(d)); derived&& drref = tc::derived_cast(tc_move_if_owned(b)); _ASSERTEQUAL(std::addressof(b), std::addressof(drref)); } ================================================ FILE: tc/base/chained.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "invoke.h" namespace tc { namespace no_adl { // Function pointers have disadvantages over function objects, in particular when being aggregated in wrapper objects: // - Invokation through a function pointer usually results in an extra indirection (like a virtual function call), // unless the compiler can figure out the address is constant over the entire lifetime of the wrapper. Note that // this is hard to prove for the compiler: The type of the wrapper is independent of the function address, so they // might be assigned from wrappers of the same type, storing pointers to different addresses. // - Many wrappers in our library support default construction (which is useful when used with STL or Boost containers). // Default construction would leave a function pointer unintitialized! template struct verify_functor final{ // TODO: We would like to verify specifically that T is a class or lambda, but unfortunately // there is no trait to test for the latter. For now, just blacklist (function-)pointers here, // as other types are not callable, anyway. static_assert(!std::is_pointer::value, "Do not pass raw function pointer as functor. Use tc_fn instead."); using type=T; }; template using verify_functor_t=typename verify_functor::type; } using no_adl::verify_functor_t; namespace no_adl { template struct [[nodiscard]] chained_impl /*not final*/ { tc::verify_functor_t> m_funcSecond; tc::verify_functor_t> m_funcFirst; template constexpr auto operator()(Args&&... args) const& return_decltype_allow_xvalue_slow_MAYTHROW( tc_invoke(m_funcSecond, tc_invoke_pack(m_funcFirst, tc_move_if_owned(args))) ) constexpr auto inverted() const& MAYTHROW { return chained_impl{ m_funcFirst.inverted(), m_funcSecond.inverted() }; } using is_transparent = void; }; } template constexpr auto chained(FuncSecond&& funcSecond, FuncFirst&& funcFirst) noexcept { return no_adl::chained_impl{tc_move_if_owned(funcSecond), tc_move_if_owned(funcFirst)}; } } ================================================ FILE: tc/base/change.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include "casts.h" #ifndef __EMSCRIPTEN__ #include #endif ///////////////////////////////////////////////////////////////////// // comparison functors, required for assign_min/assign_max namespace tc { namespace unbounded_adl { struct greatest {}; [[nodiscard]] constexpr auto operator<(tc::unused, greatest) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<=(tc::unused, greatest) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<(greatest, tc::unused) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<=(greatest, tc::unused) return_ctor_noexcept(tc::constant, ()) struct least {}; [[nodiscard]] constexpr auto operator<(tc::unused, least) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<=(tc::unused, least) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<(least, tc::unused) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<=(least, tc::unused) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<(least, greatest) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<=(least, greatest) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<(greatest, least) return_ctor_noexcept(tc::constant, ()) [[nodiscard]] constexpr auto operator<=(greatest, least) return_ctor_noexcept(tc::constant, ()) } using unbounded_adl::least; using unbounded_adl::greatest; //////////////////////////// // comparison functors // Unlike std::less<> et. al., they are special-made for comparisons, so they only use operator== and operator< with constant arguments and expect noexcept and a bool return // This avoids types having to support operator> and operator>=, which we do not want to use in our code. namespace equal_to_default { template requires std::is_class::value || std::is_class::value || std::is_pointer::value || std::is_pointer::value || std::is_same, std::remove_volatile_t>::value [[nodiscard]] constexpr auto equal_to_impl(Lhs const& lhs, Rhs const& rhs) return_decltype_MAYTHROW( tc::implicit_cast(lhs==rhs) ) template requires (!std::is_same, std::remove_volatile_t>::value) [[nodiscard]] constexpr bool equal_to_impl(Lhs const& lhs, Rhs const& rhs) noexcept { return tc::explicit_cast(lhs)==tc::explicit_cast(rhs); } } DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(equal_to) // 1. at least one of the operands is a pointer, // 2. array-to-pointer conversions, derived-to-base pointer conversions, function pointer conversions, and qualification conversions are applied as necessary to convert both operands to the same pointer type, and the resulting pointer type is an object pointer type // 3. std::nullptr_t is not ordered comparable to pointers template concept comparable_pointers = ( (std::is_pointer::value && (std::is_pointer::value || std::is_array::value)) || ((std::is_pointer::value || std::is_array::value) && std::is_pointer::value) ) && std::is_pointer>::value; template [[nodiscard]] constexpr auto less(Lhs const& lhs, Rhs const& rhs) noexcept requires requires { lhs && tc::actual_arithmetic) { auto result = tc::explicit_cast(lhs)(rhs); static_assert(std::is_same::value || std::is_same>::value || std::is_same>::value); return result; } else if constexpr (tc::comparable_pointers) { // A specialization of std::less for any pointer type yields the implementation-defined strict total order, even if the built-in < operator does not. return std::less>()(lhs, rhs); } else { static_assert( !(std::is_pointer::value && std::is_class::value && std::convertible_to) && !(std::is_class::value && std::is_pointer::value && std::convertible_to), // use std::convertible_to instead of tc::safely_convertible_to avoid any pointer comparison with operator< "if a class type is implicitly convertible to a pointer type and you want to compare this class type with the pointer type," " tc::implicit_cast class object to pointer when you want to make a pointer comparison between the two," " otherwise use specific comparison function, e.g. tc::lexicographical_compare_3way, instead of tc::less." ); auto result = lhs::value || std::is_same>::value || std::is_same>::value); return result; } } namespace no_adl { DEFINE_FN2(!tc::equal_to, fn_not_equal_to) struct[[nodiscard]] fn_less{ template [[nodiscard]] constexpr auto operator()(Lhs const& lhs, Rhs const& rhs) const& return_decltype_noexcept( tc::less(lhs,rhs) ) using is_transparent = void; using supremum = tc::greatest; using infimum = tc::least; }; DEFINE_FN2(!tc::less, fn_greater_equal) struct[[nodiscard]] fn_greater{ template [[nodiscard]] constexpr auto operator()(Lhs const& lhs, Rhs const& rhs) const& return_decltype_noexcept( tc::less(rhs,lhs) ) using is_transparent = void; using supremum = tc::least; using infimum = tc::greatest; }; struct[[nodiscard]] fn_less_equal{ template [[nodiscard]] constexpr auto operator()(Lhs const& lhs, Rhs const& rhs) const& return_decltype_noexcept( !tc::less(rhs,lhs) ) using is_transparent = void; }; } using no_adl::fn_not_equal_to; using no_adl::fn_less; using no_adl::fn_greater_equal; using no_adl::fn_greater; using no_adl::fn_less_equal; ///////////////////////////////////////////////////////////////////// // change template< typename Better, typename Var, typename Val > constexpr auto assign_better( Better better, Var&& var, Val&& val ) noexcept { return tc::make_overload( [&](tc::constant b) noexcept { static_assert( tc::safely_assignable_from ); tc_move_if_owned(var) = tc_move_if_owned(val); return b; }, [&](tc::constant b) noexcept { return b; }, [&](bool const b) noexcept { if (b) { static_assert( tc::safely_assignable_from ); tc_move_if_owned(var) = tc_move_if_owned(val); return true; } else { return false; } } )( tc_invoke(better, tc::as_const(val), tc::as_const(var)) ); } template< typename Var, typename Val > constexpr bool change( Var&& var, Val&& val ) noexcept { return tc::assign_better( [](auto const& val_, auto const& var_) noexcept { return !tc::equal_to(var_, val_); }, tc_move_if_owned(var), tc_move_if_owned(val) ); // var==val, not val==var } #ifndef __EMSCRIPTEN__ template< typename Better, typename Var, typename Val > bool assign_better( Better better, std::atomic& var, Val&& val ) noexcept { Var varOld=var; _ASSERTINITIALIZED( varOld ); while( better(tc::as_const(VERIFYINITIALIZED(val)), tc::as_const(varOld)) ) { if( var.compare_exchange_weak( varOld, val ) ) return true; } return false; } template< typename Better, typename Var, typename Val0, typename... Val > bool assign_better( Better better, std::atomic&& var, Val0&& val0, Val&&... val ) noexcept =delete; // make passing rvalue ref an error template< typename Var, typename Val > bool change( std::atomic & var, Val&& val ) noexcept { _ASSERTINITIALIZED( val ); return !tc::equal_to(var.exchange(val),val); } template< typename Var, typename Val > bool change( std::atomic&& var, Val&& val ) noexcept; // make passing rvalue ref a linker error #endif // __EMSCRIPTEN__ template< typename Better, typename Var, typename... Val> requires (1(std::initializer_list{(b=(tc::assign_better(better, var, tc_move_if_owned(val)) || b))...}); return b; } template< typename Var, typename Val0, typename... Val > constexpr bool assign_max( Var&& var, Val0&& val0, Val&&... val ) noexcept { return tc::assign_better( tc::fn_greater(), tc_move_if_owned(var), tc_move_if_owned(val0), tc_move_if_owned(val)... ); // use operator< for comparison just like tc::min/max } template< typename Var, typename Val0, typename... Val> constexpr bool assign_min( Var&& var, Val0&& val0, Val&&... val ) noexcept { return tc::assign_better( tc::fn_less(), tc_move_if_owned(var), tc_move_if_owned(val0), tc_move_if_owned(val)... ); } template void change_with_or(Var&& var, Val&& val, bool& bChanged) noexcept { // accessing an uninitialized variable is undefined behavior, so don't compare for equality if var is uninitialized! if( VERIFYINITIALIZED(bChanged) ) { _ASSERTINITIALIZED( val ); tc_move_if_owned(var) = tc_move_if_owned(val); } else { bChanged = tc::change( tc_move_if_owned(var), tc_move_if_owned(val) ); } } tc_define_fn( assign_max ); tc_define_fn( assign_min ); //////////////////////////////////// // change for float/double // Treat float/double assignment as bit pattern assignment, to avoid NaN problems. // Assigning NaN to NaN should be !bChanged. // Might not work because IEEE 754 does not specify a unique bit pattern for NaNs. // RT#5691: Also, we must not rely on comparisons involving NaN to return false, as defined by the standard, because we are using /fp:fast: // https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=518015&ppud=0&wa=wsignin1.0 template< typename Lhs, typename Rhs > [[nodiscard]] bool binary_equal( Lhs const& lhs, Rhs const& rhs ) noexcept { STATICASSERTEQUAL( sizeof(lhs), sizeof(rhs) ); return 0==std::memcmp(std::addressof(lhs),std::addressof(rhs),sizeof(lhs)); } template< typename Dst, typename Src > bool binary_change( Dst& dst, Src const& src ) noexcept { STATICASSERTEQUAL( sizeof(Dst), sizeof(src) ); if( !binary_equal(dst,src) ) { std::memcpy(std::addressof(dst),std::addressof(src),sizeof(dst)); return true; } else { return false; } } template< typename Var, typename Value > requires std::floating_point> bool change( Var&& var, Value&& value ) noexcept { using float_type = tc::decay_t; auto const nan = std::numeric_limits::quiet_NaN(); _ASSERTINITIALIZED( var ); _ASSERT( !std::isnan( var ) || binary_equal(var, nan) ); _ASSERTINITIALIZED( value ); auto float_value = tc::implicit_cast(tc_move_if_owned(value)); _ASSERT( !std::isnan( float_value ) || binary_equal(float_value, nan) ); return binary_change( var, float_value ); } } // namespace tc ================================================ FILE: tc/base/conditional.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "type_traits_fwd.h" // used in an expression, prvalue is forwarded as xvalue #define tc_conditional_impl(b, type, lhs, rhs) \ (tc::explicit_cast(b) ? static_cast(lhs) : static_cast(rhs)) // static_cast needed for conversion from const& to const&& #define tc_conditional_typed(b, lhstype, lhs, rhstype, rhs) \ tc_conditional_impl(TC_FWD(b), TC_FWD(tc::common_reference_t), TC_FWD(lhs), TC_FWD(rhs)) #define tc_conditional_rvalue_as_ref(b, lhs, rhs) \ tc_conditional_typed(b, decltype((lhs))&&, TC_FWD(lhs), decltype((rhs))&&, TC_FWD(rhs)) #define tc_conditional_prvalue_as_val(b, lhs, rhs) \ tc_conditional_typed(b, decltype((lhs)), TC_FWD(lhs), decltype((rhs)), TC_FWD(rhs)) ================================================ FILE: tc/base/conditional.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "assert_defs.h" #include "conditional.h" #define TERNARY_TYPE(first, second) \ decltype(std::declval() ? first : second) #define SAME_TYPE(first, second) \ static_assert(std::is_same::value) #define CAST_LVALUEREF(TYPE) \ static_cast(std::declval&>()) #define TERNARY_TYPE_CAST_LVALUEREF(first, second) \ TERNARY_TYPE(CAST_LVALUEREF(first), CAST_LVALUEREF(second)) #define TEST_TERNARY_STATIC_CAST_LVALUEREF(result, first, second) \ SAME_TYPE(result, TERNARY_TYPE_CAST_LVALUEREF(first, second)) TEST_TERNARY_STATIC_CAST_LVALUEREF(int, int, int); TEST_TERNARY_STATIC_CAST_LVALUEREF(int const&, int const&, int const&); TEST_TERNARY_STATIC_CAST_LVALUEREF(int&, int&, int&); TEST_TERNARY_STATIC_CAST_LVALUEREF(int&&, int&&, int&&); TEST_TERNARY_STATIC_CAST_LVALUEREF(int const&&, int const&&, int const&&); namespace { struct S {}; } TEST_TERNARY_STATIC_CAST_LVALUEREF(S, S, S); TEST_TERNARY_STATIC_CAST_LVALUEREF(S const&, S const&, S const&); TEST_TERNARY_STATIC_CAST_LVALUEREF(S&, S&, S&); TEST_TERNARY_STATIC_CAST_LVALUEREF(S&&, S&&, S&&); TEST_TERNARY_STATIC_CAST_LVALUEREF(S const&&, S const&&, S const&&); namespace { // MSVC used to create extra temporaries and report errors regarding returning of these temporaries. // Making sure this is no longer the case. // https://developercommunity.visualstudio.com/content/problem/1178976/ternary-operator-with-xvalue-operands-for-non-clas.html [[maybe_unused]] decltype(auto) foo(bool b, S&& s1, S&& s2) { return b ? static_cast(s1) : static_cast(s2); } [[maybe_unused]] decltype(auto) foo(bool b, int&& n1, int&& n2) { return b ? static_cast(n1) : static_cast(n2); } [[maybe_unused]] decltype(auto) bar(bool b, S&& s1, S&& s2) { return b ? static_cast(s1) : static_cast(s2); } [[maybe_unused]] decltype(auto) bar(bool b, int&& n1, int&& n2) { return b ? static_cast(n1) : static_cast(n2); } } ================================================ FILE: tc/base/construction_restrictiveness.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "enum.h" #include "utility.h" #include "../algorithm/minmax.h" namespace tc { //////////////////////////////////////////////// // initialization of TTarget member/element by TSource TC_DEFINE_ENUM( econstruction_t, econstruction, (FORBIDDEN)(EXPLICIT)(IMPLICIT) ); namespace construction_restrictiveness_detail { // Similar to http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4387.html : // - implicit construction == is_constructible && is_convertible, // - explicit construction == is_constructible && !is_convertible. // However, we make some unsafe conversions explicit or do not allow them at all. // Moreover, we always allow implicit construction if TTarget is constructed from a TTarget prvalue. // TODO: explicit construction, if there is a sensible definition for tc::explicit_cast(TSource) template consteval tc::econstruction_t get_construction_restrictiveness() { static_assert(!std::is_rvalue_reference::value); if constexpr( 1 == sizeof...(Args) ) { if constexpr( std::is_same, std::remove_cv_t>>>::value ) { return tc::econstructionIMPLICIT; } } if constexpr( tc::explicit_castable_from ) { return tc::implicit_constructible_from ? tc::econstructionIMPLICIT : tc::econstructionEXPLICIT; } else { return tc::econstructionFORBIDDEN; } } namespace no_adl { // MSVC up to 16.9 compilation fails if this is an alias template instead of a class template. template struct construction_restrictiveness : tc::constant()> {}; } } using construction_restrictiveness_detail::no_adl::construction_restrictiveness; namespace no_adl { // initialize N elements of TTarget by forwarding one arg per element template struct elementwise_construction_restrictiveness; template struct elementwise_construction_restrictiveness_impl final { static constexpr auto value = tc::min(econstruction, elementwise_construction_restrictiveness::value); }; template struct elementwise_construction_restrictiveness_impl final { static constexpr auto value = tc::econstructionFORBIDDEN; }; template struct elementwise_construction_restrictiveness final { static constexpr auto value = tc::econstructionIMPLICIT; }; template struct elementwise_construction_restrictiveness final { static constexpr auto value = elementwise_construction_restrictiveness_impl::value, TTarget, Args...>::value; }; } using no_adl::elementwise_construction_restrictiveness; } ================================================ FILE: tc/base/derivable.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "explicit_cast_fwd.h" #include "construction_restrictiveness.h" //----------------------------------------------------------------------------------------------------------------------------- namespace tc { ///////////////////////////////////////////// // derivable_t namespace no_adl { template struct derivable_wrapper { STATICASSERTSAME( std::remove_cvref_t, T ); static_assert( !std::is_class::value ); derivable_wrapper() noexcept {} template requires (tc::econstructionIMPLICIT==tc::construction_restrictiveness::value) derivable_wrapper(A1&& a1) noexcept : m_t(tc_move_if_owned(a1)) {} template requires (tc::econstructionEXPLICIT==tc::construction_restrictiveness::value) explicit derivable_wrapper(A1&& a1) noexcept : tc_member_init( m_t, tc_move_if_owned(a1) ) {} operator T const&() const& noexcept { return m_t; } operator T&() & noexcept { return m_t; } operator T const&&() const&& noexcept { return static_cast(m_t); } operator T&&() && noexcept { return static_cast(m_t); } private: T m_t; }; template<> struct derivable_wrapper {}; } template using derivable_t = std::conditional_t::value, T, tc::no_adl::derivable_wrapper>; #pragma push_macro("BASE_CAST_IMPL") #define BASE_CAST_IMPL(cvref) \ template requires (!std::is_class::value) \ [[nodiscard]] constexpr Dst cvref base_cast(std::type_identity_t> cvref t) noexcept { \ STATICASSERTSAME(std::remove_cvref_t, Dst); \ return static_cast(t); \ } BASE_CAST_IMPL(&) BASE_CAST_IMPL(&&) BASE_CAST_IMPL(const&) BASE_CAST_IMPL(const&&) BASE_CAST_IMPL(volatile&) BASE_CAST_IMPL(volatile&&) BASE_CAST_IMPL(volatile const&) BASE_CAST_IMPL(volatile const&&) #pragma pop_macro("BASE_CAST_IMPL") #pragma push_macro("BASE_CAST_IMPL") #define BASE_CAST_IMPL(cvref) \ template requires (!std::is_class::value) \ [[nodiscard]] constexpr Dst cvref base_cast(std::type_identity_t> cvref p) noexcept { \ STATICASSERTSAME(std::remove_cvref_t, Dst); \ return std::addressof(tc::base_cast(*p)); \ } BASE_CAST_IMPL(*) BASE_CAST_IMPL(const*) BASE_CAST_IMPL(volatile*) BASE_CAST_IMPL(volatile const*) #pragma pop_macro("BASE_CAST_IMPL") } ================================================ FILE: tc/base/empty_chain.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "fundamental.h" namespace tc { namespace empty_chain_impl { // consider a CRTP such as implements_compare etc. // // template // struct SomeCRTP : Chain {}; // // class A : SomeCRTP {}; // implicitly derives from empty_chain // class B : SomeCRTP { A myA; }; // implicitly derives from empty_chain // // then, C++ does NOT allow empty base class optimization, because the compiler has to ensure // std::addressof( base_cast(instance_of_B) ) != std::addressof( base_cast(instance_of_B.myA) ) // // therefore, force different types for each instance of empty_chain, i.e., // // template > // struct SomeCRTP : Chain {}; template struct TC_EMPTY_BASES empty_chain {}; } using empty_chain_impl::empty_chain; } ================================================ FILE: tc/base/enum.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include "casts.h" #include "integer.h" #include #include #include #include #include #include #include namespace tc { template concept contiguous_enum_type = tc::enum_type && requires(T const& value) { contiguous_enum_impl(value); }; namespace no_adl { template struct contiguous_enum : tc::constant {}; template struct contiguous_enum : tc::constant { using impl = decltype(contiguous_enum_impl(std::declval())); static constexpr auto begin() noexcept { return impl::start(); } static constexpr auto end() noexcept { // Note that we cannot use from_underlying due to a recursion. return static_cast(tc::to_underlying(impl::last()) + 1); } }; } using no_adl::contiguous_enum; template [[nodiscard]] constexpr bool is_enum_value_or_end(Integer const& n) noexcept { // reference to avoid error C4701: potentially uninitialized local variable static_assert( tc::actual_integer ); if constexpr( tc::contiguous_enum::value ) { // There are values that e can have without UB that are not one its enum values, in particular when e has a fixed underlying_type or the value fits // into the bits needed for representing the enum values: // http://stackoverflow.com/questions/18195312/what-happens-if-you-static-cast-invalid-value-to-enum-class // We do not allow such values here. return tc::explicit_cast(tc::to_underlying(tc::contiguous_enum::begin())) <= n && n <= tc::explicit_cast(tc::to_underlying(tc::contiguous_enum::end())); } else { // TODO: Implement is_enum_value_or_end(...) for all persisted enum types. return true; } } template [[nodiscard]] constexpr bool is_enum_value(Integer const& n) noexcept { static_assert( tc::actual_integer ); if constexpr( tc::contiguous_enum::value ) { return tc::explicit_cast(tc::to_underlying(tc::contiguous_enum::begin())) <= n && n < tc::explicit_cast(tc::to_underlying(tc::contiguous_enum::end())); } else { // TODO: Implement is_enum_value(...) for all persisted enum types. return true; } } template [[nodiscard]] constexpr auto from_underlying_impl(std::type_identity, tc::underlying_type_t n) { _ASSERTDEBUG(is_enum_value_or_end(n)); // _ASSERT triggers win/ARM64EC/release ICE return static_cast(n); } // integral constants until we have constexpr operator- template using enum_difference=tc::constant>(e1)-static_cast>(e2)>; template using enum_count=tc::constant::end(), tc::contiguous_enum::begin()>::value)>; } #define TC_BITMASK_OPS(Enum) \ [[nodiscard]] constexpr Enum operator&(Enum _Left, Enum _Right) \ { /* return _Left & _Right */ \ return tc::from_underlying(tc::to_underlying(_Left) & tc::to_underlying(_Right)); \ } \ \ [[nodiscard]] constexpr Enum operator|(Enum _Left, Enum _Right) \ { /* return _Left | _Right */ \ return tc::from_underlying(tc::to_underlying(_Left) | tc::to_underlying(_Right)); \ } \ \ [[nodiscard]] constexpr Enum operator^(Enum _Left, Enum _Right) \ { /* return _Left ^ _Right */ \ return tc::from_underlying(tc::to_underlying(_Left) ^ tc::to_underlying(_Right)); \ } \ \ [[nodiscard]] constexpr Enum operator~(Enum _Left) \ { /* return ~_Left */ \ return tc::from_underlying(~tc::to_underlying(_Left)); \ } \ \ constexpr Enum& operator&=(Enum& _Left, Enum _Right) \ { /* return _Left &= _Right */ \ _Left = _Left & _Right; \ return _Left; \ } \ \ constexpr Enum& operator|=(Enum& _Left, Enum _Right) \ { /* return _Left |= _Right */ \ _Left = _Left | _Right; \ return _Left; \ } \ \ constexpr Enum& operator^=(Enum& _Left, Enum _Right) \ { /* return _Left ^= _Right */ \ _Left = _Left ^ _Right; \ return _Left; \ } \ \ [[nodiscard]] constexpr bool HasAllOf(Enum _Left, Enum _Right) \ { /* return _Left HasAllOf _Right */\ return !static_cast(~_Left & _Right); \ }\ \ std::weak_ordering operator<=>(Enum, Enum) = delete; \ bool operator<(Enum, Enum) = delete; \ bool operator<=(Enum, Enum) = delete; \ bool operator>=(Enum, Enum) = delete; \ bool operator>(Enum, Enum) = delete; #ifdef TC_PRIVATE struct CSaveHandler; struct CXmlReader; #endif namespace tc { template< typename Enum> requires requires { tc::contiguous_enum::end(); } constexpr void assert_not_end(Enum e) noexcept { _ASSERTDEBUG( // There are values that e can have without UB that are not one its enum values, in particular when e has // a fixed underlying_type or the value fits into the bits needed for representing the enum values: // http://stackoverflow.com/questions/18195312/what-happens-if-you-static-cast-invalid-value-to-enum-class // We don't allow such values here tc::is_enum_value(tc::to_underlying(e)) ); } template< typename Enum> requires requires { tc::contiguous_enum::end(); } [[nodiscard]] constexpr Enum verify_not_end(Enum e) noexcept { tc::assert_not_end(e); return e; } namespace enumset_adl { template< typename Enum > struct enumset; } using enumset_adl::enumset; } #define TC_DEFINE_CONTIGUOUS_ENUM(Enum, enumStart, enumLast) \ [[nodiscard]] inline auto contiguous_enum_impl(Enum const&) { \ struct impl { \ static constexpr Enum start() noexcept { return (enumStart); } \ static constexpr Enum last() noexcept { return (enumLast); } \ }; \ return impl{}; \ } \ [[nodiscard]] inline bool check_initialized_impl(Enum const& e) noexcept { /*reference to avoid error C4701: potentially uninitialized local variable*/ \ return tc::is_enum_value_or_end(tc::to_underlying(e)); \ } \ [[nodiscard]] constexpr tc::int_value_least_t< tc::enum_count::value > operator-(Enum const e1, Enum const e2) noexcept { \ return static_cast::value >>(tc::to_underlying(e1)-tc::to_underlying(e2)); \ } \ template \ [[nodiscard]] constexpr tc::enumset operator|(Enum lhs, Enum rhs) noexcept { \ tc::enumset sete(lhs); \ return sete|=rhs; \ } \ template \ [[nodiscard]] constexpr tc::enumset operator&(Enum lhs, Enum rhs) noexcept { \ tc::enumset sete(lhs); \ return sete&=rhs; \ } \ constexpr Enum& operator++(Enum& e) noexcept { \ _ASSERTDEBUG( e!=tc::contiguous_enum::end() ); \ e = tc::from_underlying( tc::to_underlying(e)+1); \ return e; \ } \ constexpr Enum& operator--(Enum& e) noexcept { \ _ASSERTDEBUG( e!=tc::contiguous_enum::begin() ); \ e = tc::from_underlying( tc::to_underlying(e)-1); \ return e; \ } \ template \ constexpr Enum& operator+=(Enum& e, N const& n) noexcept { \ e = tc::from_underlying( tc::add( tc::to_underlying(e), n ) ); \ return e; \ } \ template \ constexpr Enum& operator-=(Enum& e, N const& n) noexcept { \ e = tc::from_underlying( tc::sub( tc::to_underlying(e), n ) ); \ return e; \ } \ template \ [[nodiscard]] constexpr Enum operator+(Enum e, N const& n) noexcept { \ e+=n; \ return e; \ } \ template \ [[nodiscard]] constexpr Enum operator-(Enum e, N const& n) noexcept { \ e-=n; \ return e; \ } \ template \ [[nodiscard]] constexpr auto operator~(Enum e) noexcept { \ tc::assert_not_end(e); \ if constexpr( 2 == tc::enum_count::value ) { \ return tc::contiguous_enum::end() - (e - tc::contiguous_enum::begin() + 1); \ } else { \ return ~tc::enumset(e); \ } \ } #ifdef _DEBUG #define TC_PREFIX_CONSTANT_STRING( _, prefix, constant ) #prefix #constant #define TC_DEFINE_ENUM_REPORTSTREAM_PIPE( Enum, prefix, constants ) \ namespace Enum ## _detail { \ /* TODO: move c_map back as static local after MSVC compiler bug is solved: https://developercommunity.visualstudio.com/t/code-generation-bug-on-static-variable-i/10541326 */ \ inline constexpr char const* c_map[]={ \ BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(TC_PREFIX_CONSTANT_STRING, prefix, constants)), \ "tc::contiguous_enum<" #Enum ">::end()" \ }; \ } \ [[nodiscard]] inline char const* enum_literal(Enum e) noexcept { \ return Enum ## _detail::c_map[e-tc::contiguous_enum::begin()]; \ } #else #define TC_DEFINE_ENUM_REPORTSTREAM_PIPE( Enum, prefix, constants ) #endif // pair == ( Enum, prefix ) // need to forward BOOST_PP_TUPLE_ELEM result two times, otherwise concatenation does not work #define TC_DEFINE_ENUM_CONSTANT_INTERN_INTERN( Enum, prefix, constant ) inline constexpr Enum prefix##constant = Enum :: constant; #define TC_DEFINE_ENUM_CONSTANT_INTERN(a,b,c) TC_DEFINE_ENUM_CONSTANT_INTERN_INTERN(a,b,c) #define TC_DEFINE_ENUM_CONSTANT( state, pair, constant ) TC_DEFINE_ENUM_CONSTANT_INTERN( BOOST_PP_TUPLE_ELEM(2, 0, pair), BOOST_PP_TUPLE_ELEM(2, 1, pair), constant) namespace tc::enum_detail { template constexpr auto max_value_for_underlying_type(IntOffset nOffset, int const nConstants) noexcept { static_assert( std::is_signed::value, "unsigned underlying type not supported" ); // Assume two's complement. // -1 - nOffset, like ~nOffset never overflows. auto const nAbsBeginValue = nOffset < 0 ? ~nOffset : nOffset; // Can store an additional negative value auto const nEndValue = nOffset + nConstants; return nAbsBeginValue < nEndValue ? nEndValue : nAbsBeginValue; // Avoids dependency on tc::max. } } #define TC_DEFINE_SCOPED_ENUM_WITH_OFFSET( Enum, prefix, offset, constants ) \ namespace Enum ## _adl { \ enum class Enum : tc::int_value_least_t { \ BOOST_PP_SEQ_HEAD(constants) = offset, \ BOOST_PP_SEQ_ENUM( BOOST_PP_SEQ_TAIL(BOOST_PP_SEQ_PUSH_BACK(constants, _END) ) ) \ }; \ TC_DEFINE_CONTIGUOUS_ENUM(Enum, Enum::BOOST_PP_SEQ_HEAD(constants), static_cast(tc::to_underlying(Enum::_END) - 1)) \ TC_DEFINE_ENUM_REPORTSTREAM_PIPE( Enum, prefix, constants ) \ } \ using Enum ## _adl::Enum; #define TC_DEFINE_SCOPED_ENUM( Enum, prefix, constants ) \ TC_DEFINE_SCOPED_ENUM_WITH_OFFSET( Enum, prefix, 0, constants ) #define TC_DEFINE_ENUM_WITH_OFFSET( Enum, prefix, offset, constants ) \ TC_DEFINE_SCOPED_ENUM_WITH_OFFSET( Enum, prefix, offset, constants ) \ BOOST_PP_SEQ_FOR_EACH(TC_DEFINE_ENUM_CONSTANT, (Enum, prefix), constants) #define TC_DEFINE_ENUM( Enum, prefix, constants ) \ TC_DEFINE_ENUM_WITH_OFFSET( Enum, prefix, 0, constants ) #define TC_DEFINE_UNPREFIXED_ENUM( Enum, constants ) \ TC_DEFINE_ENUM(Enum, , constants) namespace tc { namespace no_adl { template struct sub_enum_trait; template struct is_sub_enum_of /* not final */: tc::constant {}; template requires std::is_same>::super, std::remove_cvref_t>::value struct is_sub_enum_of /* not final */ : tc::constant {}; template requires (!std::is_same>::super, std::remove_cvref_t>::value) struct is_sub_enum_of /* not final */ : is_sub_enum_of>::super, std::remove_cvref_t> {}; } using no_adl::is_sub_enum_of; namespace explicit_convert_adl { template requires tc::is_sub_enum_of::value constexpr EnumSuper explicit_convert_impl(adl_tag_t, std::type_identity, EnumSub const esub) noexcept { return static_cast(esub); // cast from sub to super is always safe } template requires tc::is_sub_enum_of::value constexpr EnumSub explicit_convert_impl(adl_tag_t, std::type_identity, EnumSuper const esuper) noexcept { return tc::from_underlying(tc::to_underlying(esuper)); } template requires tc::is_sub_enum_of::value constexpr std::optional explicit_convert_impl(adl_tag_t, std::type_identity>, EnumSuper const esuper) noexcept { if (auto const n = tc::to_underlying(esuper); tc::is_enum_value(n)) { return static_cast(n); } else { return std::nullopt; } } } } #define TC_VERIFY_EQUALITY_BETWEEN_ENUMSUPER_AND_ENUMSUB(state, pair, constant) \ static_assert(tc::to_underlying(BOOST_PP_TUPLE_ELEM(2, 0, pair)::constant) == tc::to_underlying(BOOST_PP_TUPLE_ELEM(2, 1, pair)::constant)); #define TC_DEFINE_SUB_ENUM(EnumSuper, EnumSub, prefixsub, constants) \ TC_DEFINE_ENUM_WITH_OFFSET(EnumSub, prefixsub, tc::to_underlying(EnumSuper::BOOST_PP_SEQ_HEAD(constants)), constants) \ BOOST_PP_SEQ_FOR_EACH(TC_VERIFY_EQUALITY_BETWEEN_ENUMSUPER_AND_ENUMSUB, (EnumSuper, EnumSub), constants) \ namespace tc::no_adl { \ template<> \ struct sub_enum_trait final { \ using super = EnumSuper; \ }; \ } ================================================ FILE: tc/base/enum.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "assert_defs.h" #include "enum.h" #include "../enumset.h" #include "../unittest.h" #define TEST_ENUM(offset, constants, underlying) \ namespace { namespace BOOST_PP_CAT(enum_test_, __COUNTER__) { \ TC_DEFINE_ENUM_WITH_OFFSET(Enum, en, offset, constants); \ static_assert( std::is_signed>::value ); \ STATICASSERTEQUAL( sizeof(std::underlying_type_t), sizeof(underlying) ); \ STATICASSERTEQUAL( tc::to_underlying(tc::contiguous_enum::begin()), offset ); \ STATICASSERTEQUAL( tc::to_underlying(tc::contiguous_enum::end()), offset + BOOST_PP_SEQ_SIZE(constants) ); \ } } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused" // unused functions/variables defined in unnamed namespace #endif TEST_ENUM(0, (a), std::int8_t); TEST_ENUM(126, (a), std::int8_t); TEST_ENUM(-128, (a), std::int8_t); TEST_ENUM(127, (a), std::int16_t); TEST_ENUM(-129, (a), std::int16_t); TEST_ENUM(-0x8000, (a), std::int16_t); TEST_ENUM(-0x10000, (a), std::int32_t); TEST_ENUM(0x10000, (a), std::int32_t); TEST_ENUM(-0x100000000, (a), std::int64_t); TEST_ENUM(0x100000000, (a), std::int64_t); #define CONSTANTS4(pre) (pre ## 0)(pre ## 1)(pre ## 2)(pre ## 3) #define CONSTANTS16(pre) CONSTANTS4(pre ## 0)CONSTANTS4(pre ## 1)CONSTANTS4(pre ## 2)CONSTANTS4(pre ## 3) #define CONSTANTS64(pre) CONSTANTS16(pre ## 0)CONSTANTS16(pre ## 1)CONSTANTS16(pre ## 2)CONSTANTS16(pre ## 3) #define CONSTANTS192 CONSTANTS64(a ## 0)CONSTANTS64(a ## 1)CONSTANTS64(a ## 2) TEST_ENUM(-128, CONSTANTS192, std::int8_t); TEST_ENUM(-129, CONSTANTS192, std::int16_t); TEST_ENUM(0, CONSTANTS192, std::int16_t); namespace { TC_DEFINE_ENUM_WITH_OFFSET(ETest, etest, 1234, (A)(B)(C)(D)(E)(F)); constexpr auto etestEND = tc::contiguous_enum::end(); UNITTESTDEF(enumset_index_inc_dec) { tc::enumset setetest; setetest |= etestB; setetest |= etestC; setetest |= etestE; auto itetest = tc::begin(setetest); _ASSERTEQUAL(*itetest++, etestB); _ASSERTEQUAL(*itetest++, etestC); _ASSERTEQUAL(*itetest++, etestE); _ASSERTEQUAL(itetest, tc::end(setetest)); _ASSERTEQUAL(*--itetest, etestE); _ASSERTEQUAL(*--itetest, etestC); _ASSERTEQUAL(*--itetest, etestB); _ASSERTEQUAL(itetest, tc::begin(setetest)); } } #ifdef __clang__ #pragma clang diagnostic pop #endif ================================================ FILE: tc/base/explicit_cast.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "explicit_cast_fwd.h" #include "casts.h" #include "noncopyable.h" #include "trivial_functors.h" #include "../tuple.h" #include #include namespace tc { DEFINE_FN2_TMPL( explicit_cast, (typename) ) namespace explicit_convert_adl { template constexpr TTarget explicit_convert_impl(adl_tag_t, std::type_identity, TSource src) noexcept { static_assert(tc::decayed); _ASSERTE(tc::char_in_range(tc::to_underlying(src))); _ASSERTE(tc::char_in_range(tc::to_underlying(src))); return static_cast(src); } template constexpr TTarget explicit_convert_impl(adl_tag_t, std::type_identity, TSource src) noexcept { TTarget target=static_cast(src); _ASSERTDEBUG(target==src); // default round-to-zero from floating point to integer is wrong most of the time, so we force rounding first return target; } template constexpr TTarget explicit_convert_impl(adl_tag_t, std::type_identity, TSource src) noexcept { return static_cast(src); // silence warning for int to float conversion } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wsign-compare" #else MODIFY_WARNINGS_BEGIN( ((disable)(4018)) // signed/unsigned mismatch ((disable)(4388)) // signed/unsigned mismatch ) #endif template constexpr TTarget explicit_convert_impl(adl_tag_t, std::type_identity, TSource src) noexcept { _ASSERTE( ( !std::is_signed::value || ( std::is_signed::value ? std::numeric_limits::lowest() <= src : /*must be signed 0 to avoid conversion of src to unsigned*/0 <= src ) ) && // conversion to unsigned (Warning 4018 and 4388) is ok here: src <= std::numeric_limits::max() ); return static_cast(src); } #ifdef __clang__ #pragma clang diagnostic pop #else MODIFY_WARNINGS_END #endif template std::streamoff explicit_convert_impl(adl_tag_t, std::type_identity, std::fpos const& pos) noexcept { return pos; } // tc::constant template requires tc::explicit_castable_from constexpr tc::constant explicit_convert_impl(adl_tag_t, std::type_identity>, TSource&& source) noexcept { _ASSERTE(t == tc::explicit_cast(tc_move_if_owned(source))); return {}; } } namespace return_cast_detail { namespace no_adl { template struct [[nodiscard]] return_cast_impl final : tc::nonmovable { tc::tuple m_tuple; template TTarget> constexpr operator TTarget() && return_MAYTHROW( tc_apply(tc::fn_explicit_cast>(), tc_move(m_tuple)) ) constexpr return_cast_impl&& operator+() && noexcept { return static_cast(*this); } }; } template constexpr no_adl::return_cast_impl return_cast(Args&&... args) noexcept { return { {}, tc::tie(tc_move_if_owned(args)...) }; } } // operator+ casts to xvalue to disallow tc_return_cast in function with auto return type. #define tc_return_cast/*(...)*/ \ return +tc::return_cast_detail::return_cast /*(__VA_ARGS__)*/ DEFINE_FN2_TMPL( reluctant_explicit_cast, (typename) ) namespace no_adl { template struct [[nodiscard]] lazy_explicit_cast final : tc::nonmovable { tc::tuple m_tuple; // std::remove_cv affects only values and leaves const/volatile references untouched, which is what we want. constexpr operator TTarget() && return_MAYTHROW( tc_apply(tc::fn_explicit_cast>(), tc_move(m_tuple)) ) }; template struct is_value_safely_constructible&&> : tc::constant {}; } template requires tc::explicit_castable_from constexpr tc::no_adl::lazy_explicit_cast lazy_explicit_cast(Args&&... args) noexcept { return { {}, tc::tie(tc_move_if_owned(args)...) }; } template requires tc::explicit_castable_from constexpr decltype(auto) reluctant_lazy_explicit_cast(Arg&& arg) noexcept { if constexpr(tc::safely_constructible_from) { return tc_move_if_owned(arg); } else { return tc::lazy_explicit_cast(tc_move_if_owned(arg)); } } namespace detail { template(tc::safely_constructible_from)>* = nullptr> // TODO CWG2369: https://cplusplus.github.io/CWG/issues/2369.html; static_cast to workaround gcc bug constexpr auto with_lazy_explicit_cast_impl(Func func, Args&&... args) return_decltype_MAYTHROW( func(tc_move_if_owned(args)...) ) template(tc::safely_constructible_from)>* = nullptr> // TODO CWG2369: https://cplusplus.github.io/CWG/issues/2369.html; static_cast to workaround gcc bug constexpr auto with_lazy_explicit_cast_impl(Func func, Args&&... args) return_decltype_MAYTHROW( func(tc::lazy_explicit_cast(tc_move_if_owned(args)...)) ) } template constexpr auto with_lazy_explicit_cast(Func&& func, Args&&... args) return_decltype_MAYTHROW( tc::detail::with_lazy_explicit_cast_impl(tc_move_if_owned(func), tc_move_if_owned(args)...) ) namespace explicit_convert_adl { // std::pair template requires tc::explicit_castable_from && tc::explicit_castable_from constexpr auto explicit_convert_impl(adl_tag_t, std::type_identity>, TSourceFirst&& first, TSourceSecond&& second) noexcept( noexcept(tc::explicit_cast(tc_move_if_owned(first))) && noexcept(tc::explicit_cast(tc_move_if_owned(second))) ) { return std::pair( tc::reluctant_lazy_explicit_cast(tc_move_if_owned(first)), tc::reluctant_lazy_explicit_cast(tc_move_if_owned(second)) ); } // std::optional template requires tc::explicit_castable_from constexpr std::optional explicit_convert_impl(adl_tag_t, std::type_identity>, std::in_place_t, TSource&&... src) MAYTHROW { return tc::with_lazy_explicit_cast ( [](auto&&... args) return_ctor_MAYTHROW(std::optional, (std::in_place, tc_move_if_owned(args)...)), tc_move_if_owned(src)... ); } template constexpr auto explicit_convert_impl(adl_tag_t, std::type_identity>, TSource&& source) return_decltype_MAYTHROW( source ? tc::explicit_cast>(std::in_place, *tc_move_if_owned(source)) : std::nullopt ) } template constexpr void optional_emplace(std::optional& o, Args&& ... args) noexcept(noexcept(tc::explicit_cast(tc_move_if_owned(args)...))) { tc::with_lazy_explicit_cast( [&](auto&&... args2) MAYTHROW { o.emplace(tc_move_if_owned(args2)...); }, tc_move_if_owned(args)... ); } } ================================================ FILE: tc/base/explicit_cast_fwd.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "type_traits_fwd.h" #include "template_func.h" #include "invoke.h" #include ////////////////////////////////////////////////////////////////// // generic initialization and assignment between different types // // Copy/move/assignment cannot be overloaded for third party types // or builtins. // // If tc::explicit_cast is used to convert a builtin numeric type S into // another builtin numeric type T, rounding and range checks are // applied. // // For other types, the default behavior of explicit_cast is // A a(tc::explicit_cast(b)) is equivalent to A(a) , // a=tc::explicit_cast::type>(b) is equivalent to a=b , // i.e., the argument b is forwarded to the copy/move/assignment operators // of A. // // Specialize explicit_convert if additional conversions for specific target // types are required. namespace tc { DEFINE_TAG_TYPE(fill_tag) DEFINE_TAG_TYPE(range_tag) namespace no_adl { template struct char_limits; template<> struct char_limits { static constexpr std::size_t c_nMaxCodeUnitsPerCodePoint = 4; [[nodiscard]] static constexpr bool interval_in_range(unsigned int nFirst, unsigned int nLast) noexcept { return nLast <= 0x7f; } }; template<> struct char_limits { static constexpr std::size_t c_nMaxCodeUnitsPerCodePoint = 2; [[nodiscard]] static constexpr bool interval_in_range(unsigned int nFirst, unsigned int nLast) noexcept { return nLast <= 0xd7ff || (0xe000 <= nFirst && nLast <= 0xffff); } }; template<> struct char_limits { static constexpr std::size_t c_nMaxCodeUnitsPerCodePoint = 1; [[nodiscard]] static constexpr bool interval_in_range(unsigned int nFirst, unsigned int nLast) noexcept { return nLast <= 0xd7ff || (0xe000 <= nFirst && nLast <= 0x10ffff); } }; struct char_limits_undefined_dummy {}; template<> struct char_limits : std::conditional_t< 2 == sizeof(wchar_t), char_limits, std::conditional_t< 4 == sizeof(wchar_t), char_limits, char_limits_undefined_dummy > > {}; } using no_adl::char_limits; template [[nodiscard]] constexpr bool char_in_range(unsigned int const n) noexcept { return char_limits::interval_in_range(n, n); } namespace explicit_convert_default { // This is the default explicit_convert, not a specialization, because aggregate initialization must have lower priority than other specializations which could also be aggregate types // Aggregate types are tc::safely_constructible_from same type (copy/move). This is handled in the default explicit_cast below. template requires std::is_class::value && std::is_aggregate::value constexpr auto explicit_convert_impl(std::type_identity, Args&&... args) return_decltype_MAYTHROW( TTarget{tc_move_if_owned(args)...} ) template std::array explicit_convert_impl(std::type_identity>, Args&&... args) = delete; // explicitly delete direct list construction of std::array } DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(explicit_convert) namespace no_adl { template struct fn_internal_explicit_cast final { template, Args&&...>>* = nullptr> constexpr auto operator()(Args&&... args) const& return_decltype_MAYTHROW(tc::explicit_convert(std::type_identity>(), tc_move_if_owned(args)...)) template requires tc::safely_constructible_from, Args&&...> constexpr auto operator()(Args&&... args) const& return_ctor_MAYTHROW(std::remove_cv_t, (tc_move_if_owned(args)...)) }; } template [[nodiscard]] constexpr auto explicit_cast(auto&&... args) return_decltype_MAYTHROW( tc_invoke_pack(no_adl::fn_internal_explicit_cast(), tc_move_if_owned(args)) ) template concept explicit_castable_from = requires { tc::explicit_cast(std::declval()...); }; // Using decltype(this->member) ensures casting to the correct type even if member is shadowed (but disallows base classes). #define tc_member_init(member, ...) member(tc::explicit_castmember)>(__VA_ARGS__)) namespace no_adl { struct bool_context final { template< typename T > constexpr bool_context(T const& t) noexcept : tc_member_init(m_b, t) {} constexpr operator bool() const& noexcept { return m_b; } private: bool m_b; }; } using no_adl::bool_context; template [[nodiscard]] constexpr decltype(auto) reluctant_explicit_cast(TSource&& src) noexcept { STATICASSERTSAME(std::remove_cvref_t, TTarget); if constexpr( tc::decayed_derived_from ) { return tc_move_if_owned(src); } else { return tc::explicit_cast(tc_move_if_owned(src)); } } ////////////////////////////////////////////////////////////////////////// // concepts template concept predicate = tc::invocable const&> && requires(Func const& f, std::remove_cvref_t const& t) { tc::explicit_cast(tc_invoke(f, t)); }; template concept nothrow_predicate = tc::predicate && tc::nothrow_invocable const&>; template concept constant_predicate = tc::predicate && requires(Func const& f, std::remove_cvref_t const& t) { tc::explicit_cast(decltype(tc_invoke(f, t))::value); }; template concept constant_predicate_true = tc::constant_predicate && tc::explicit_cast(decltype(tc_invoke(std::declval(), std::declval const&>()))::value); template concept constant_predicate_false = tc::constant_predicate && !tc::explicit_cast(decltype(tc_invoke(std::declval(), std::declval const&>()))::value); template concept runtime_predicate = tc::predicate && (!tc::constant_predicate); } ================================================ FILE: tc/base/functors.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include "invoke.h" #include #include #include #include #include //////////////////////////////// // functor equivalents for operators, free functions and member functions #define DEFINE_FN2( func, name ) \ struct [[nodiscard]] name { \ template< typename... Args > \ constexpr auto operator()( Args&& ... args) /*no &*/ const \ return_decltype_allow_xvalue_MAYTHROW( func(tc_move_if_owned(args)...) ) \ using is_transparent=void; \ }; #define DEFINE_MEM_FN_BODY_( ... ) \ template< typename O, typename... __A > \ constexpr auto operator()( O&& o, __A&& ... __a ) const \ return_decltype_allow_xvalue_MAYTHROW( tc_move_if_owned(o) __VA_ARGS__ ( tc_move_if_owned(__a)... ) ) // std::mem_fn (C++11 standard 20.8.2) knows the type it can apply its member function pointer to and // dereferences via operator* until it reaches that something of that type. We cannot do that because // we have no type. Merely checking for the presence of the member function name is dangerous because // changes in otherwise unrelated types (the iterator and its pointee) would influence the lookup. // Instead we distinguish between shallow member access, implemented in dot_mem_fn, and deep // member access via operator->, with the small addition that if operator-> does not exist, we use the // the dot operator, in the spirit of dereferencing as much as possible. #define DEFINE_MEM_FN_AUTO_BODY_( ... ) \ template< typename O, typename... __A, std::enable_if_t::value>* = nullptr > \ constexpr auto operator()( O&& o, __A&& ... __a ) const \ return_decltype_allow_xvalue_MAYTHROW( tc_move_if_owned(o)-> __VA_ARGS__ ( tc_move_if_owned(__a)... ) ) \ template< typename O, typename... __A, std::enable_if_t::value>* = nullptr > \ constexpr auto operator()( O&& o, __A&& ... __a ) const \ return_decltype_allow_xvalue_MAYTHROW( tc_move_if_owned(o). __VA_ARGS__ ( tc_move_if_owned(__a)... ) ) // When a functor must be declared in class-scope, e.g., to access a protected member function, // you should use DEFINE_MEM_FN instead of tc_define_fn. // tc_define_fn always has to define a function named "void func(define_fn_dummy_t)" which may // shadow inherited members named func. #define DEFINE_MEM_FN( func ) \ struct [[nodiscard]] dot_mem_fn_ ## func { \ DEFINE_MEM_FN_BODY_( .func ) \ }; \ struct [[nodiscard]] mem_fn_ ## func { \ DEFINE_MEM_FN_AUTO_BODY_( func ) \ }; #define tc_define_fn( func ) \ static void func(tc::define_fn_dummy_t) noexcept = delete; \ DEFINE_FN2( func, fn_ ## func ) \ DEFINE_MEM_FN( func ) #define MEM_FN_TEMPLATE_DECLARATION(r, data, i, elem) BOOST_PP_COMMA_IF(i) elem T##i #define MEM_FN_TEMPLATE_INVOCATION(r, data, i, elem) BOOST_PP_COMMA_IF(i) T##i #define DEFINE_FN2_TMPL( func, tmpl ) \ template< BOOST_PP_SEQ_FOR_EACH_I( MEM_FN_TEMPLATE_DECLARATION, _, tmpl ) > \ DEFINE_FN2( func< BOOST_PP_SEQ_FOR_EACH_I( MEM_FN_TEMPLATE_INVOCATION, _, tmpl ) >, fn_ ## func ) // See comments above for DEFINE_MEM_FN #define DEFINE_MEM_FN_TMPL( func, tmpl ) \ template< BOOST_PP_SEQ_FOR_EACH_I( MEM_FN_TEMPLATE_DECLARATION, _, tmpl ) > \ struct [[nodiscard]] dot_mem_fn_ ## func { \ DEFINE_MEM_FN_BODY_( .template func< BOOST_PP_SEQ_FOR_EACH_I( MEM_FN_TEMPLATE_INVOCATION, _, tmpl ) > ) \ }; \ template< BOOST_PP_SEQ_FOR_EACH_I( MEM_FN_TEMPLATE_DECLARATION, _, tmpl ) > \ struct [[nodiscard]] mem_fn_ ## func { \ DEFINE_MEM_FN_AUTO_BODY_( template func< BOOST_PP_SEQ_FOR_EACH_I( MEM_FN_TEMPLATE_INVOCATION, _, tmpl ) > ) \ }; #define DEFINE_FN_TMPL( func, tmpl ) \ template< BOOST_PP_SEQ_FOR_EACH_I( MEM_FN_TEMPLATE_DECLARATION, _, tmpl ) > \ static void func(tc::define_fn_dummy_t) noexcept = delete; \ DEFINE_FN2_TMPL( func, tmpl ) \ DEFINE_MEM_FN_TMPL( func, tmpl ) #define tc_fn(...) ([](auto&&... args) return_decltype_allow_xvalue_MAYTHROW(__VA_ARGS__(tc_move_if_owned(args)...))) namespace tc { constexpr bool is_chained_member_access_expression(char const* const strExpr) noexcept { // This is a heuristic with false positives (e.g. complex template parameters). auto nArrowCount = 0; for (auto it = strExpr; *it; ++it) { if ((')' == it[0] || ']' == it[0]) && ('.' == it[1] || ('-' == it[1] && '>' == it[2]) || '[' == it[1] || '(' == it[1])) { // We access the result of a function call, which might be a prvalue. return true; } else if ('-' == it[0] && '>' == it[1]) { ++nArrowCount; if (1 < nArrowCount) { // Chaining arrows is problematic, as an overloaded operator-> might create a temporary. // (Chaining dot is fine as it's not overloadable). return true; } } } return false; } namespace no_adl { template struct mem_fn_return_type { using type = Return; }; template struct mem_fn_return_type { static_assert(!bIsChainedMemberAccessExpression, "tc_mem_fn/tc_member cannot contain chained member function calls that ultimately return an xvalue"); using type = tc::rewrap_temporary_t; }; } template using mem_fn_return_type_t = typename no_adl::mem_fn_return_type::type; #define tc_mem_fn_impl(bIsChained, Obj, ...) noexcept(noexcept(__VA_ARGS__)) -> tc::mem_fn_return_type_t { \ return __VA_ARGS__; \ } } #define tc_mem_fn(...) ([](auto&& _, auto&&... args) tc_mem_fn_impl(tc::is_chained_member_access_expression(#__VA_ARGS__), decltype(_), \ tc_unwrap_temporary(tc_move_if_owned(_))__VA_ARGS__(tc_move_if_owned(args)...) \ )) #define tc_member(...) ([](auto&& _) tc_mem_fn_impl(tc::is_chained_member_access_expression(#__VA_ARGS__), decltype(_), \ tc_unwrap_temporary(tc_move_if_owned(_))__VA_ARGS__ \ )) namespace tc { namespace no_adl { template struct [[nodiscard]] TC_EMPTY_BASES overload : std::remove_cvref_t... { using std::remove_cvref_t::operator()...; constexpr overload(F&&... f) noexcept : std::remove_cvref_t(tc_move_if_owned(f))... {} }; } using no_adl::overload; template [[nodiscard]] constexpr auto make_overload(F&&... f) noexcept { return overload(tc_move_if_owned(f)...); } } DEFINE_FN2( operator delete, fn_operator_delete ) // Cannot use DEFINE_FN2_TMPL here, since casts are build in and the compiler //(both clang and MSVC) does not accept passing a parameter pack to *_cast #pragma push_macro("DEFINE_CAST_") #define DEFINE_CAST_(name, constexpr_) \ namespace no_adl { \ template \ struct [[nodiscard]] fn_ ## name final { \ template \ constexpr_ auto operator()(From&& from) const return_decltype_allow_xvalue_MAYTHROW( tc_rewrap_temporary(From, name(tc_unwrap_temporary(tc_move_if_owned(from)))) ) \ }; \ } \ using no_adl::fn_ ## name; namespace tc { DEFINE_CAST_(static_cast, constexpr) DEFINE_CAST_(reinterpret_cast, /*constexpr*/) DEFINE_CAST_(const_cast, constexpr) } #pragma pop_macro("DEFINE_CAST_") namespace tc { namespace no_adl { struct [[nodiscard]] fn_subscript final { template constexpr auto operator()( Lhs&& lhs, Rhs&& rhs ) const& return_decltype_allow_xvalue_MAYTHROW( tc_rewrap_temporary(Lhs, tc_unwrap_temporary(tc_move_if_owned(lhs))[tc_unwrap_temporary(tc_move_if_owned(rhs))]) ) }; } using no_adl::fn_subscript; #pragma push_macro("PREFIX_FN_") #define PREFIX_FN_( name, op ) \ namespace no_adl { \ struct [[nodiscard]] fn_ ## name final { \ template \ constexpr auto operator()(T&& t ) const \ return_decltype_allow_xvalue_MAYTHROW( tc_rewrap_temporary(T, op tc_unwrap_temporary(tc_move_if_owned(t))) ) \ }; \ } \ using no_adl::fn_ ## name; PREFIX_FN_( logical_not, ! ) PREFIX_FN_( indirection, * ) PREFIX_FN_( increment, ++ ) PREFIX_FN_( decrement, -- ) #pragma pop_macro("PREFIX_FN_") #pragma push_macro("INFIX_FN_") #define INFIX_FN_( name, op ) \ namespace no_adl { \ struct [[nodiscard]] fn_ ## name final { \ template \ constexpr auto operator()(Lhs&& lhs, Rhs&& rhs ) const& \ return_decltype_allow_xvalue_MAYTHROW( tc_rewrap_temporary(TC_FWD(Lhs, Rhs), tc_unwrap_temporary(tc_move_if_owned(lhs)) op tc_unwrap_temporary(tc_move_if_owned(rhs))) ) \ }; \ } \ using no_adl::fn_ ## name; INFIX_FN_( bit_or, | ) INFIX_FN_( bit_and, & ) INFIX_FN_( bit_xor, ^ ) INFIX_FN_( plus, + ) INFIX_FN_( minus, - ) INFIX_FN_( mul, * ) INFIX_FN_( logical_or, || ) INFIX_FN_( logical_and, && ) // tc::fn_div defined in round.h #pragma pop_macro("INFIX_FN_") #pragma push_macro("ASSIGN_FN_") #define ASSIGN_FN_( name, op ) \ namespace no_adl { \ struct [[nodiscard]] fn_ ## name final { \ template \ constexpr auto operator()(Lhs&& lhs, Rhs&& rhs ) const& \ return_decltype_allow_xvalue_MAYTHROW( tc_rewrap_temporary(Lhs, tc_unwrap_temporary(tc_move_if_owned(lhs)) op tc_unwrap_temporary(tc_move_if_owned(rhs))) ) \ }; \ } \ using no_adl::fn_ ## name; ASSIGN_FN_( assign_bit_or, |= ) ASSIGN_FN_( assign_bit_and, &= ) ASSIGN_FN_( assign_bit_xor, ^= ) ASSIGN_FN_( assign_plus, += ) ASSIGN_FN_( assign_minus, -= ) ASSIGN_FN_( assign_mul, *= ) // tc::fn_assign_div defined in round.h ASSIGN_FN_( assign, = ) #pragma pop_macro("ASSIGN_FN_") #pragma push_macro("FN_LOGICAL_") #define FN_LOGICAL_(name, op) \ namespace no_adl { \ struct [[nodiscard]] fn_assign_logical_ ## name { \ template \ constexpr decltype(auto) operator()(Lhs&& lhs, Rhs&& rhs) const& noexcept(noexcept(tc_unwrap_temporary(lhs) = tc_unwrap_temporary(tc_move_if_owned(lhs)) op tc_unwrap_temporary(tc_move_if_owned(rhs)))) { \ tc_unwrap_temporary(lhs) = tc_unwrap_temporary(tc_move_if_owned(lhs)) op tc_unwrap_temporary(tc_move_if_owned(rhs)); \ return tc_move_if_owned(lhs); \ } \ }; \ } \ using no_adl::fn_assign_logical_ ## name; FN_LOGICAL_(or, ||) FN_LOGICAL_(and, &&) #pragma pop_macro("FN_LOGICAL_") } ================================================ FILE: tc/base/fundamental.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include #include #include #include #ifdef _MSC_VER #define MODIFY_WARNING_HELPER(r, data, subseq) __pragma( warning( BOOST_PP_SEQ_ELEM(0, subseq): BOOST_PP_SEQ_ELEM(1, subseq) ) ) #define MODIFY_WARNINGS(seq) BOOST_PP_SEQ_FOR_EACH(MODIFY_WARNING_HELPER, _, seq) #define MODIFY_WARNINGS_BEGIN(seq) \ __pragma( warning( push ) ) \ MODIFY_WARNINGS(seq) #define MODIFY_WARNINGS_END __pragma( warning( pop ) ) #else #define MODIFY_WARNINGS(seq) #define MODIFY_WARNINGS_BEGIN(seq) #define MODIFY_WARNINGS_END #endif #ifdef _MSC_VER // https://devblogs.microsoft.com/cppblog/optimizing-the-layout-of-empty-base-classes-in-vs2015-update-2-3/ #define TC_EMPTY_BASES __declspec(empty_bases) #else #define TC_EMPTY_BASES #endif #if 201803L <= __has_cpp_attribute(unlikely) // There is also the [[likely]] attribute, but we prefer to only annotate unlikely code paths to avoid redundancy. #define TC_UNLIKELY [[unlikely]] #else #define TC_UNLIKELY #endif #ifdef _MSC_VER #define TC_FORCEINLINE [[msvc::forceinline]] #else #define TC_FORCEINLINE [[gnu::always_inline, gnu::nodebug]] #endif #define THROW(...) noexcept(false) // MAYTHROW is introduced for those very generic functions which may throw only with a few types, but we cannot // handle the exceptions in those functions. The exceptions should be handled where the function is called. #define MAYTHROW noexcept(false) // #define FOO(x) FOO_(TC_FWD(x)) // Always use TC_FWD instead of parentheses to forward an argument to another macro. If the expansion of x has // unshielded comma(s), TC_FWD(x) will still be recognized as a single argument by FOO_ and then forward x // to FOO_ with its comma(s), but without putting extra parentheses around x as FOO_((x)) would do. #define TC_FWD(...) __VA_ARGS__ namespace tc { // Used for explicitly discarding return value from function decorated with [[nodiscard]] attribute. // Also used to suppress 'unused variable' warnings, if no better option available. template constexpr void discard(T&&) noexcept{} } #ifdef _MSC_VER // MSVC generates null checks when upcasting this (https://godbolt.org/z/o6zhWaKGo). #define MSVC_WORKAROUND_THIS (__assume(nullptr != this), this) #else #define MSVC_WORKAROUND_THIS this #endif #ifdef _MSC_VER // MSVC seems to mistake the .template memfn<...>(...) syntax for the beginning of a template class. // Supposedly, this has been fixed in VS2019, but we still see it happening in a few places. #define MSVC_TEMPLATE_WORKAROUND #else #define MSVC_TEMPLATE_WORKAROUND template #endif #ifdef _MSC_VER #define MSVC_WORKAROUND // comment out this line for Microsoft internal compiler unit test #endif #ifdef MSVC_WORKAROUND #define IF_MSVC_WORKAROUND(...) __VA_ARGS__ #define IF_NO_MSVC_WORKAROUND(...) #define IF_MSVC_WORKAROUND_ELSE(argMsvc, argNoMsvc) argMsvc #else #define IF_MSVC_WORKAROUND(...) #define IF_NO_MSVC_WORKAROUND(...) __VA_ARGS__ #define IF_MSVC_WORKAROUND_ELSE(argMsvc, argNoMsvc) argNoMsvc #endif #ifndef __clang__ #define TC_REQUIRES_CWG2369_WORKAROUND(cond) > requires (cond) #else #define TC_REQUIRES_CWG2369_WORKAROUND(cond) , /*not requires because of CWG issue 2369*/ std::enable_if_t<(cond)>* = nullptr> #endif #if defined(TC_PRIVATE) && defined(_MSC_VER) // Please update *.natvis on modification #define no_adl NQ #define cont_emplace_back_detail NQ0 #define transform_adaptor_adl NQ1 #define concat_adaptor_adl NQ2 #define RefT_adl NQ3 #define generator_range_output_adaptor_adl NQ4 #define interval_adl NQ5 #define empty_range_adl NQ6 #define select_range_adaptor_adl NQ7 #define tuple_adl NQ8 #define _precT_adl NQ9 #define point_adl NQA #define counting_iterator_adl NQB #define cartesian_product_adaptor_adl NQC #define union_adaptor_adl NQD #define dense_map_adl NQE #define append_no_adl NQF #define explicit_convert_adl NQG #define for_each_adl NQH #define size_proxy_adl NQI #define expanding_invoke_adl NQJ #define literal_range_adl NQK #define as_constexpr_no_adl NQL #define less_key_adl NQM #define lohi_adl NQN #define sign_adl NQO #define adjacent_adaptor_adl NQP #define cartesian_product_adaptor_detail NQQ #define reverse_adaptor_adl NQR #define tuple_detail NQS #define debug_output_impl_ns NQT #define debug_output_adl NQU #define hash_append_detail NQV #define hash_append_adl NQW #define enumset_adl NQX #define make_lazy_adl NQY #define static_auto_constexpr_no_adl NQZ #define invoke_detail NQa #define SNamedPropT_adl NQb #define _fill_adl NQc #define integral_as_padded_dec_adl NQd #define section_entry_detail NQe #define restricted_enum_adl NQf #define _linestyle_adl NQg #define index_iterator_impl NQh #define SGridline_adl NQi #define void_generator_type_check_no_adl NQj #define EnterReportSection_detail NQk //NQl #define static_vector_adl NQm #define com_ptr_adl NQn #define EDateFormatType_adl NQo #define value_epsilon_adl NQp #define generator_range_adl NQq #define find_first_if_detail NQr #define convert_enc_impl NQs #define iterator_facade_adl NQt #define unique_adaptor_adl NQu #define strong_typedef_adl NQv #define explicit_convert_std_array_detail NQw #endif ================================================ FILE: tc/base/generic_macros.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TC_EXPAND(...) __VA_ARGS__ #define TC_PP_APPLY_MACRO(state, macro, elem) macro(elem) // According to boost reference https://www.boost.org/doc/libs/1_73_0/libs/preprocessor/doc/ref/seq_nil.html, // BOOST_PP_SEQ_NIL is only a placeholder for BOOST_PP_SEQ_PUSH_BACK. // We should not use BOOST_PP_SEQ_NIL as an empty sequence when calling BOOST_PP_SEQ_xxx macros (except BOOST_PP_SEQ_PUSH_BACK). // However, TC_PP_SEQ_xxx macros support BOOST_PP_SEQ_NIL as an empty sequence. We should use TC_PP_SEQ_xxx if the sequence can be NIL. #define TC_PP_SEQ_SIZE(seq) BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(BOOST_PP_SEQ_PUSH_BACK(seq,~))) #define TC_PP_SEQ_IS_NIL(seq) BOOST_PP_EQUAL(TC_PP_SEQ_SIZE(seq),0 ) #define TC_PP_IF(cond, t, f) BOOST_PP_EXPAND(TC_FWD BOOST_PP_IF(cond, (t), (f))) #define TC_PP_IF_SEQ_NOT_NIL(seq, ...) TC_PP_IF(TC_PP_SEQ_IS_NIL(seq), , TC_FWD(__VA_ARGS__)) #define TC_PP_SEQ_HELPER_PUSH_FRONT_TO_NIL(seq, elem) (elem) #define TC_PP_SEQ_PUSH_FRONT(seq, elem) \ BOOST_PP_IF(TC_PP_SEQ_IS_NIL(seq), TC_PP_SEQ_HELPER_PUSH_FRONT_TO_NIL, BOOST_PP_SEQ_PUSH_FRONT)(seq, elem) #define TC_PP_SEQ_HELPER_TO_NIL(seq) BOOST_PP_SEQ_NIL #define TC_PP_SEQ_POP_BACK(seq) \ BOOST_PP_IF(BOOST_PP_DEC(TC_PP_SEQ_SIZE(seq)), BOOST_PP_SEQ_POP_BACK, TC_PP_SEQ_HELPER_TO_NIL)(seq) #define TC_PP_SEQ_CAT(seq) \ BOOST_PP_IF(TC_PP_SEQ_IS_NIL(seq), BOOST_PP_EAT, BOOST_PP_SEQ_CAT)(seq) #define TC_PP_SEQ_FOR_EACH(macro, data, seq) \ BOOST_PP_IF(TC_PP_SEQ_IS_NIL(seq), BOOST_PP_EAT, BOOST_PP_SEQ_FOR_EACH)(macro, data, seq) #define TC_PP_SEQ_ENUM(seq) \ BOOST_PP_IF(TC_PP_SEQ_IS_NIL(seq), BOOST_PP_EAT, BOOST_PP_SEQ_ENUM)(seq) #define TC_PP_SEQ_TRANSFORM(op, data, seq) \ BOOST_PP_IF(TC_PP_SEQ_IS_NIL(seq), BOOST_PP_EAT, BOOST_PP_SEQ_TRANSFORM)(op, data, seq) // expands to "macro(elem1)macro(elem2)...macro(elemN)" #define TC_PP_CAT_TRANSFORMED_SEQ(macro, seq) \ BOOST_PP_IF(TC_PP_SEQ_IS_NIL(seq), BOOST_PP_EAT, TC_PP_SEQ_CAT)(TC_PP_SEQ_TRANSFORM(TC_PP_APPLY_MACRO, macro, seq)) // expands to "op(elem1), op(elem2), ...., op(elemN)" #define TC_PP_ENUM_TRANSFORMED_SEQ(op, data, seq) \ BOOST_PP_IF(TC_PP_SEQ_IS_NIL(seq), BOOST_PP_EAT, BOOST_PP_SEQ_ENUM)(TC_PP_SEQ_TRANSFORM(op, data, seq)) // expands to "macro(elem1) macro(elem2) macro(elemN)" #define TC_PP_UNPACK_TRANSFORMED_SEQ(macro, seq) \ TC_PP_SEQ_FOR_EACH(TC_PP_APPLY_MACRO, macro, seq) // expands to "(macro(elem))(delimiter)" #define TC_PP_HELPER_DELIMIT_TRANSFORMED_SEQ(state, pair, elem) \ (BOOST_PP_SEQ_ELEM(0, pair)(elem))(BOOST_PP_SEQ_ELEM(1, pair)) // expands to "macro(elem1) delimiter macro(elem2) delimiter ... delimiter macro(elemN)" #define TC_PP_DELIMIT_TRANSFORMED_SEQ(macro, delimiter, seq) \ TC_PP_UNPACK_TRANSFORMED_SEQ( \ TC_EXPAND, \ TC_PP_SEQ_POP_BACK(BOOST_PP_IF( \ TC_PP_SEQ_SIZE(seq), \ TC_PP_SEQ_FOR_EACH(TC_PP_HELPER_DELIMIT_TRANSFORMED_SEQ, (macro)(delimiter), seq), /*(macro(elem1))(delimiter)...(delimiter)(macro(elemN))(delimiter)*/ \ BOOST_PP_SEQ_NIL \ )) /*(macro(elem1))(delimiter)...(delimiter)(macro(elemN))*/ \ ) // expands a parameter seq into its pack: // ((type)(...)(name)) --> ... // ((type)(name)) --> #define TC_PP_PARAM_PACK(seq) \ BOOST_PP_IF(BOOST_PP_EQUAL(TC_PP_SEQ_SIZE(seq),3), BOOST_PP_SEQ_ELEM, BOOST_PP_EAT)(1, seq) // expands a parameter seq into its type: // ((type)(...)(name)) --> type // ((type)(name)) --> type #define TC_PP_PARAM_TYPE(seq) \ BOOST_PP_SEQ_ELEM(0, seq) // expands a parameter seq into its type pack: // ((type)(...)(name)) --> type ... // ((type)(name)) --> type #define TC_PP_PARAM_TYPE_PACK(seq) \ TC_PP_PARAM_TYPE(seq) TC_PP_PARAM_PACK(seq) // expands a parameter seq into its name: // ((type)(...)(name)) --> name // ((type)(name)) --> name #define TC_PP_PARAM_NAME(seq) \ BOOST_PP_SEQ_HEAD(BOOST_PP_SEQ_REVERSE(seq)) // expands a parameter seq into its name pack: // ((type)(...)(name)) --> name ... // ((type)(name)) --> name #define TC_PP_PARAM_NAME_PACK(seq) \ TC_PP_PARAM_NAME(seq) TC_PP_PARAM_PACK(seq) // expands a parameter seq: // ((type)(...)(name)) --> type ... name // ((type)(name)) --> type name #define TC_PP_PARAM(seq) \ TC_PP_PARAM_TYPE_PACK(seq) TC_PP_PARAM_NAME(seq) // expands a variable pair into its type: // ((type)(name)) --> type #define TC_PP_PAIR_VAR_TYPE(pairTypeName) \ BOOST_PP_SEQ_ELEM(0, pairTypeName) // expands a variable pair into its name: // ((type)(name)) --> name #define TC_PP_PAIR_VAR_NAME(pairTypeName) \ BOOST_PP_SEQ_ELEM(1, pairTypeName) // expands a variable pair: // ((type)(name)) --> type name #define TC_PP_PAIR_VAR(pairTypeName) \ TC_PP_PAIR_VAR_TYPE(pairTypeName) TC_PP_PAIR_VAR_NAME(pairTypeName) // expands a variable pair into its definition (with semicolon, useful for defining members from a list of pairs) // ((type)(name)) --> type name; #define TC_PP_PAIR_VAR_DEFINE(pairTypeName) \ TC_PP_PAIR_VAR(pairTypeName); // expands parameter seq to enum. e.g. (template|function definition) parameter list supporting optional parameter pack: // "type1 (...)? name1, type2 (...)? name2, ... , typeN (...)? nameN" #define TC_PP_PARAMS_ENUM(seqArgs) \ TC_PP_ENUM_TRANSFORMED_SEQ(TC_PP_APPLY_MACRO, TC_PP_PARAM, seqArgs) // expands parameter seq to argument enum (losing the types). e.g. (template|function call) argument list supporting optional parameter pack: // "name1 (...)?, name2 (...)?, ... , nameN (...)?" #define TC_PP_PARAMS_ARG_ENUM(seqArgs) \ TC_PP_ENUM_TRANSFORMED_SEQ(TC_PP_APPLY_MACRO, TC_PP_PARAM_NAME_PACK, seqArgs) // expands parameter seq to type enum (losing the names). e.g. (template template|function decl) parameter list supporting optional parameter pack: // "type1 (...)?, type2 (...)?, ... , typeN (...)? #define TC_PP_PARAMS_TYPE_ENUM(seqArgs) \ TC_PP_ENUM_TRANSFORMED_SEQ(TC_PP_APPLY_MACRO, TC_PP_PARAM_TYPE_PACK, seqArgs) #define TC_OP_PAIR_TO_SPACE_SEPARATED_PAIR(state, data, pair) \ BOOST_PP_SEQ_ELEM(0, pair) BOOST_PP_SEQ_ELEM(1, pair) #define TC_OP_PAIR_TO_1ST_ELEMENT(state, data, pair) \ BOOST_PP_SEQ_ELEM(0, pair) #define TC_OP_PAIR_TO_2ND_ELEMENT(state, data, pair) \ BOOST_PP_SEQ_ELEM(1, pair) // Macro for accessing nameless union members within MS structs, which we can't control (can't name the union). // In Mac nameless union is not allowed, so the union has a name. #ifdef __clang__ #define WIN32_UNION_MEMBER(_obj_ptr, _member) \ (_obj_ptr)->DUMMYUNIONNAME._member #else #define WIN32_UNION_MEMBER(_obj_ptr, _member) \ (_obj_ptr)->_member #endif // Use if you have to check if an expression compiles, e.g., to check if an operator is defined, a cast is valid etc #define TC_HAS_EXPR_TEMPLATE_PARAMETER(Type) typename Type #define TC_HAS_EXPR(name, seqArgs, ...) \ template< BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(TC_PP_APPLY_MACRO, TC_HAS_EXPR_TEMPLATE_PARAMETER, seqArgs)) > \ concept has_ ## name = requires { __VA_ARGS__; }; ================================================ FILE: tc/base/has_xxx.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include #include #define TC_HAS_MEM_FN_XXX_CONCEPT_DEF( name, decoration, ... ) \ template \ concept has_mem_fn_ ## name = requires { std::declval().name( __VA_ARGS__ ); }; #define TC_HAS_STATIC_FN_XXX_CONCEPT_DEF( name, ... ) \ template \ concept has_static_fn_ ## name = requires { T::name(__VA_ARGS__); }; ================================================ FILE: tc/base/inplace.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "noncopyable.h" #include "has_xxx.h" #include "move.h" #include "type_traits_fwd.h" #include // Defined as a macro, to preserve the short-circuiting semantics of && and || // Example: // bool b = ...; // tc_inplace(b) && bool_convertible_condition() // tc_inplace(b) || bool_convertible_condition() #define tc_inplace(expr) \ (expr)=VERIFYINITIALIZED(expr) namespace tc { namespace inplace_adl { template< typename T > struct [[nodiscard]] inplace final : tc::noncopyable { constexpr explicit inplace(T& t) noexcept:m_t(VERIFYINITIALIZED(t)){} T& m_t; }; } using inplace_adl::inplace; #pragma push_macro("DEFINE_INPLACE_OPERATOR") #define DEFINE_INPLACE_OPERATOR(op, name) \ TC_HAS_MEM_FN_XXX_CONCEPT_DEF( name, & ) \ \ namespace inplace_adl { \ template< typename T > \ constexpr void operator op(inplace t) noexcept { \ t.m_t=op tc_move_always(t.m_t); \ } \ \ template< has_mem_fn_ ## name T > \ constexpr void operator op(inplace t) noexcept { \ t.m_t.name(); \ } \ \ template< typename T > \ void operator op(inplace> t) noexcept { \ static_assert( !has_mem_fn_ ## name ); \ T tCurrent = t.m_t.load(std::memory_order_relaxed); \ while (!t.m_t.compare_exchange_weak(tCurrent, op tCurrent)) \ {} \ } \ } \ \ template \ constexpr T name(T t) noexcept { \ op tc::inplace(t); \ return t; \ } DEFINE_INPLACE_OPERATOR(-, negate) DEFINE_INPLACE_OPERATOR(~, bitwise_not) DEFINE_INPLACE_OPERATOR(!, logical_not) #pragma pop_macro("DEFINE_INPLACE_OPERATOR") } ================================================ FILE: tc/base/inside_unwinding.h ================================================ #pragma once #include "assert_defs.h" #include namespace tc { namespace no_adl { struct inside_unwinding { inside_unwinding() noexcept : m_nUncaughtExceptions(std::uncaught_exceptions()) {} inside_unwinding(inside_unwinding const&) noexcept : inside_unwinding() {} inside_unwinding& operator=(inside_unwinding const&) & noexcept { return *this; } bool inside_stack_unwinding() const& noexcept { int const nUncaughtExceptions = std::uncaught_exceptions(); _ASSERT(m_nUncaughtExceptions<=nUncaughtExceptions); return m_nUncaughtExceptions struct int_least; template requires (nBits<=std::numeric_limits::digits) struct int_least { using type=typename boost::int_t::least; // boost::int_t::least uses bit-width including sign bit }; template struct uint_least; template requires (nBits<=std::numeric_limits::digits) struct uint_least { using type=typename boost::uint_t::least; }; } // nBits is the number of radix-2 digits the type can represent without change. It does not include sign bit. nBits<=std::numeric_limits>::digits. template using int_least_t = typename no_adl::int_least::type; template using uint_least_t = typename no_adl::uint_least::type; namespace actual_integer_like_detail { template inline constexpr bool actual_integer_like_impl = tc::actual_integer; } template concept actual_integer_like = actual_integer_like_detail::actual_integer_like_impl; namespace floating_point_like_detail { template inline constexpr bool floating_point_like_impl = std::floating_point; } template concept floating_point_like = floating_point_like_detail::floating_point_like_impl; // checks whether arithmetic operations Lhs op Rhs preserve values of Lhs and Rhs (vs. silently converting one to unsigned) template< typename Lhs, typename Rhs > struct arithmetic_operation_preserves_sign : tc::constant< // std::is_signed is false for non-arithmetic (e.g., user-defined) types. In this case, we check that the result of addition is signed. This presumably ensures that there is no silent conversion to unsigned. (!std::is_signed::value && !std::is_signed::value) || std::is_signed()+std::declval())>::value > { static_assert(tc::actual_integer); static_assert(tc::actual_integer); }; static_assert( !tc::arithmetic_operation_preserves_sign::value ); static_assert( tc::arithmetic_operation_preserves_sign::value ); template constexpr auto prepare_argument(T t) noexcept { if constexpr( tc::arithmetic_operation_preserves_sign::value ) { return t; } else { return tc::as_unsigned(t); } } template< typename Lhs, tc::actual_integer Rhs> requires tc::actual_integer || tc::char_type [[nodiscard]] constexpr Lhs add( Lhs lhs, Rhs rhs ) noexcept { if constexpr( std::is_signed::value ) { // does not rely on implementation-defined truncation of integers tc_return_cast( lhs+tc::explicit_cast< std::make_signed_t>(rhs) ); } else { // modulo semantics, both arguments are zero- or sign-extended to unsigned and the result truncated return static_cast(lhs+rhs); } } template< typename Lhs, tc::actual_integer Rhs> requires tc::actual_integer || tc::char_type [[nodiscard]] constexpr Lhs sub( Lhs lhs, Rhs rhs ) noexcept { if constexpr( std::is_signed::value ) { // does not rely on implementation-defined truncation of integers tc_return_cast( lhs-tc::explicit_cast< std::make_signed_t>(rhs) ); } else { // modulo semantics, both arguments are zero- or sign-extended to unsigned and the result truncated return static_cast(lhs-rhs); } } template constexpr T& assign_add(T& lhs, T rhs) noexcept { static_assert( tc::actual_integer ); _ASSERTE( lhs < 0 ? std::numeric_limits::lowest() - lhs <= rhs : rhs <= std::numeric_limits::max() - lhs ); return lhs += rhs; } template constexpr T& assign_mul(T& lhs, T rhs) noexcept { static_assert( tc::actual_integer ); _ASSERTE( lhs <= 0 ? rhs <= 0 ? 0 == lhs || std::numeric_limits::max() / lhs <= rhs : 0 == rhs || std::numeric_limits::lowest() / rhs <= lhs : rhs <= 0 ? std::numeric_limits::lowest() / lhs <= rhs : rhs <= std::numeric_limits::max() / lhs ); return lhs *= rhs; } template [[nodiscard]] constexpr auto pow(B const base, E exp) noexcept -> decltype(base * base) { static_assert( tc::actual_integer ); static_assert( tc::actual_integer ); using R = decltype(base * base); R result = 1; if( 0 != exp ) { _ASSERTE( 0 < exp ); R rbase = base; for (;;) { if( 1 == exp % 2 ) { tc::assign_mul(result, rbase); } exp /= 2; if( 0 == exp ) { break; } tc::assign_mul(rbase, rbase); } } else { _ASSERTE( 0 != base ); } return result; } } ================================================ FILE: tc/base/invoke.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "utility.h" #include "tag_type.h" #include #include #include #include namespace tc { ////////////////////////////////////////////////////////////////////////// // invoke namespace invoke_detail { namespace no_adl { template struct expanded final { using indices = std::index_sequence<0>; template static constexpr Arg&& select(Arg&& arg) noexcept { STATICASSERTEQUAL(I, 0); return tc_move_if_owned(arg); } }; template requires tc::tuple_like> struct expanded final { using indices = std::make_index_sequence>::value>; template static constexpr decltype(auto) select(Tuple&& tpl) noexcept { // While tc_decrement_lifetime, tc_increment_lifetime are correct here, it increases compile time of tcaddin by 20% on Mac. return /*tc_decrement_lifetime(*/tc::get(/*tc_increment_lifetime(*/tc_move_if_owned(tpl))/*))*/; } }; } using no_adl::expanded; // We use ADL to workaround cyclic dependencies. namespace expanding_invoke_adl { DEFINE_ADL_TAG_TYPE(tag) } namespace no_adl { struct overload_temporary {}; struct overload_remove_temporary {}; struct overload_expanded {}; } using no_adl::overload_temporary; using no_adl::overload_remove_temporary; using no_adl::overload_expanded; // Simplify using requires on invoke once xcode implements CWG issue 2369. template constexpr auto select_overload() noexcept { if constexpr (bForceExpand) { return overload_expanded{}; } else if constexpr (requires(Func&& func, Args&&... args) { tc_move_if_owned(func)(tc_move_if_owned(args)...); }) { using result_type = decltype(std::declval()(std::declval()...)); static_assert(!tc::instance_tn, tc::temporary>, "function returns a dangling temporary"); if constexpr (std::is_rvalue_reference::value) { return overload_temporary{}; // if the return type when called with xvalues is an xvalue, function might want to react to temporaries } else { return overload_remove_temporary{}; } } else if constexpr ((tc::tuple_like || ...)) { return overload_expanded{}; } } template using selected_overload = decltype(select_overload...>()); #pragma push_macro("return_invoke") // We do want to return a tc::temporary, so can't use the return_decltype_* macros // (We have to use decltype(auto) + requires due to a GCC compiler bug.) #define return_invoke(...) noexcept(noexcept(__VA_ARGS__)) -> decltype(auto) requires requires { __VA_ARGS__; } { return __VA_ARGS__; } template , overload_temporary>>* = nullptr> constexpr auto invoke(Func&& func, Args&&... args) return_invoke( tc_decrement_lifetime(tc_move_if_owned(func)(tc_increment_lifetime(tc_move_if_owned(args))...)) ) template , overload_remove_temporary>>* = nullptr> constexpr auto invoke(Func&& func, Args&&... args) return_decltype_MAYTHROW( tc_move_if_owned(func)(tc_unwrap_temporary(tc_move_if_owned(args))...) ) template , overload_expanded>>* = nullptr> constexpr auto invoke(Func&& func, Args&&... args) return_invoke( /* ADL to resolve cyclic dependency */expanding_invoke_impl(expanding_invoke_adl::tag, tc::repeat_integer_sequence::indices::size()...>, std::index_sequence_for>{}, tc::concat_index_sequence::indices...>{}, tc_move_if_owned(func), tc_move_if_owned(args)... ) ) namespace expanding_invoke_adl { template constexpr auto expanding_invoke_impl(tag_t, std::index_sequence, std::index_sequence, Func&& func, Args&&... args) return_invoke( tc::invoke_detail::invoke(tc_move_if_owned(func), tc_prvalue_as_temporary(expanded, ArgIdxs>>::template select(tc::select_nth(tc_move_if_owned(args)...)))... ) ) } #pragma pop_macro("return_invoke") } #define tc_invoke_argument(r, data, i, expr) BOOST_PP_COMMA_IF(i) tc_prvalue_as_temporary(expr) #define tc_invoke_argument_list(...) BOOST_PP_SEQ_FOR_EACH_I(tc_invoke_argument, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) // Calls a function, expanding tuple arguments into components (even recursively), if necessary. #define tc_invoke(func, ...) (tc::invoke_detail::invoke(func, tc_invoke_argument_list(__VA_ARGS__))) #define tc_invoke_pack(func, ...) (tc::invoke_detail::invoke(func, tc_invoke_argument_list(__VA_ARGS__) ... )) // Like tc_invoke, but expands all arguments at least once. #define tc_apply(func, ...) (tc::invoke_detail::invoke(func, tc_invoke_argument_list(__VA_ARGS__))) #define tc_apply_pack(func, ...) (tc::invoke_detail::invoke(func, tc_invoke_argument_list(__VA_ARGS__) ... )) ////////////////////////////////////////////////////////////////////////// // concepts template using invoke_result_t = decltype(tc_invoke_pack(std::declval(), std::declval>())); template concept invocable = requires { typename invoke_result_t; }; template concept nothrow_invocable = tc::invocable && noexcept(tc_invoke_pack(std::declval(), std::declval>())); } ================================================ FILE: tc/base/invoke.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "assert_defs.h" #include "invoke.h" #include "casts.h" #include "../tuple.h" namespace { template struct test_arguments { mutable int foo; template int&& /* we return an rvalue reference, otherwise invoke would not give us temporaries */ operator()(Args&&...) const noexcept { ([]{ STATICASSERTSAME(ExpectedArgs&&, Args&&); }(), ...); return tc_move(foo); } }; [[maybe_unused]] void static_tests_invoke_arguments() noexcept { int i; tc::tuple tpl; tc_invoke( (test_arguments, tc::temporary, char const(&)[4], int&&, int const&>{}), 1, true, "abc", tc_move(i), tc::as_const(i) ); tc_invoke( (test_arguments, 1>, tc::temporary, 1>, tc::tuple&, tc::temporary, 1>>{}), tc::make_tuple(), tc::make_tuple(0), tpl, tc::tie(i) ); tc_apply( (test_arguments, int&, int&>{}), tc::make_tuple(), tc::make_tuple(0), tpl, tc::tie(i) ); tc_apply( (test_arguments, int&>{}), (tc::temporary, 1>(tc::make_tuple(0))), (tc::temporary, 1>(tc::tie(i))) ); } [[maybe_unused]] void static_tests_invoke() noexcept { tc_static_auto_constexpr_lambda(rvalue_sink) = [](int, double&&, char const&) noexcept {}; static_assert(!tc::invocable); static_assert(!tc::invocable>); tc_invoke(rvalue_sink, 0, 0., 'a'); tc_invoke(rvalue_sink, tc::make_tuple(0, 0., 'a')); tc_invoke(rvalue_sink, tc::make_tuple(0, 0.), 'a'); tc_invoke(rvalue_sink, tc::make_tuple(0, tc::make_tuple(tc::make_tuple(0.), 'a'))); tc_static_auto_constexpr_lambda(lvalue_sink) = [](int, double&, char const&) noexcept {}; static_assert(!tc::invocable); static_assert(!tc::invocable>); static_assert(!tc::invocable>); int a0 = 0; double a1 = 0.; char a2 = 'a'; tc_invoke(lvalue_sink, a0, a1, a2); tc_invoke(lvalue_sink, tc::tie(a0, a1, a2)); tc_invoke(lvalue_sink, tc::tie(a0, a1), a2); tc_invoke(lvalue_sink, tc::tie(a0, tc::tie(tc::tie(a1), a2))); } [[maybe_unused]] void static_tests_apply() noexcept { // We need the lambda to test tc_apply without any arguments - the macro requires at least one. tc_static_auto_constexpr_lambda(apply) = [](auto fn, auto&& ... args) noexcept { tc_apply_pack(fn, tc_move_if_owned(args)); }; apply([](auto const& ... args) noexcept { STATICASSERTEQUAL(sizeof...(args), 4); }, tc::make_tuple(0, tc::make_tuple(1, 2), 3), 4); apply([]{}); apply([]{}, tc::make_tuple(), tc::make_tuple()); } } ================================================ FILE: tc/base/invoke_with_constant.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "type_traits_fwd.h" #include "utility.h" #include "../algorithm/size.h" #include "../range/iota_range.h" namespace tc { namespace invoke_with_constant_impl { template TResult invoke_impl(Func func, Args&&... args) MAYTHROW { return func(tc::constant(), tc_move_if_owned(args)...); } template inline constexpr TIndex IdxFirst = I; template struct invoke_with_constant_impl {}; template struct invoke_with_constant_impl> { template struct inner { using result_type = tc::common_reference_t< decltype(std::declval&>()(tc::constant(), std::declval()...))... >; template static result_type constexpr invoke_constexpr(Func&& func, TIndex nIndex, Args&&... args) MAYTHROW { if (I == nIndex) { return func(tc::constant(), tc_move_if_owned(args)...); // MAYTHROW } else if constexpr (0 < sizeof...(IsRemaining)) { return invoke_constexpr(tc_move_if_owned(func), nIndex, tc_move_if_owned(args)...); // MAYTHROW } else { _ASSERTFALSE; return func(tc::constant(), tc_move_if_owned(args)...); } } static result_type constexpr invoke_constexpr_outer(Func&& func, TIndex nIndex, Args&&... args) MAYTHROW { return invoke_constexpr(tc_move_if_owned(func), nIndex, tc_move_if_owned(args)...); // MAYTHROW (but won't because constexpr) } private: // TODO: move back as static local after MSVC compiler bug is fixed: https://developercommunity.visualstudio.com/t/code-generation-bug-on-static-variable-i/10541326 // tc_static_auto_constexpr with std::array triggers pdb problem. static constexpr std::add_pointer_t, Args&&...)> c_apfn[] = { invoke_impl, Args...> ... }; public: static result_type invoke_non_constexpr(Func&& func, TIndex nIndex, Args&&... args) MAYTHROW { auto const nTableIndex = nIndex - IdxFirst; _ASSERTNORETURN(0 <= nTableIndex && nTableIndex < tc::size(c_apfn)); return c_apfn[nTableIndex](tc_move_if_owned(func), tc_move_if_owned(args)...); // MAYTHROW } }; }; } template decltype(auto) constexpr invoke_with_constant(Func&& func, typename ContiguousIntegerSequence::value_type nIndex, Args&&... args) MAYTHROW { static_assert(tc::is_contiguous_integer_sequence::value); if constexpr (0 < ContiguousIntegerSequence::size()) { using impl_type = typename invoke_with_constant_impl::invoke_with_constant_impl::template inner; if (std::is_constant_evaluated()) { return impl_type::invoke_constexpr_outer(tc_move_if_owned(func), nIndex, tc_move_if_owned(args)...); } else { return impl_type::invoke_non_constexpr(tc_move_if_owned(func), nIndex, tc_move_if_owned(args)...); } } } template decltype(auto) invoke_with_constant(Func func, Enum e, Args&&... args) MAYTHROW { return tc::invoke_with_constant())>>( [&](auto constn) MAYTHROW -> decltype(auto) { return tc_invoke_pack(func, tc::constant(), decltype(constn)::value)>(), tc_move_if_owned(args)); }, tc::all_values().index_of(e) ); } } ================================================ FILE: tc/base/large_integer.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "integer.h" #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wsign-conversion" #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wcomma" #else MODIFY_WARNINGS_BEGIN(((disable)(4459))) // declaration hides global declaration #endif #include #ifdef __clang__ #pragma clang diagnostic pop #else MODIFY_WARNINGS_END #endif namespace tc { namespace no_adl { template requires (std::numeric_limits::digits { using type=boost::multiprecision::number>; }; template requires (std::numeric_limits::digits { using type=boost::multiprecision::number>; }; } namespace actual_integer_like_detail { template inline constexpr bool actual_integer_like_impl >> = true; } } ================================================ FILE: tc/base/modified.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "type_traits_fwd.h" #include "temporary.h" namespace tc { template concept inplace_modifiable = !std::is_lvalue_reference>::value && !std::is_const>>::value; template [[nodiscard]] constexpr decltype(auto) modified_impl(T&& t, Fn fn) MAYTHROW { if constexpr (inplace_modifiable) { fn(tc_unwrap_temporary(t)); return tc_move(t); } else { auto tCopy = tc::decay_copy(tc_unwrap_temporary(t)); fn(tCopy); return tCopy; } } } #define tc_modified(obj, ...) tc::modified_impl(obj, [&](auto& _) MAYTHROW -> void { __VA_ARGS__; }) ================================================ FILE: tc/base/modified.t.cpp ================================================ // think-cell public library // // Copyright (C) 2016-2023 think-cell Software GmbH // // Distributed under 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 #include "assert_defs.h" #include "modified.h" #include "../unittest.h" UNITTESTDEF(tc_modified) { int lvalue = 0; static_assert(!tc::inplace_modifiable); _ASSERTEQUAL(tc_modified(lvalue, _ = 11), 11); _ASSERTEQUAL(lvalue, 0); int const clvalue = 0; static_assert(!tc::inplace_modifiable); _ASSERTEQUAL(tc_modified(clvalue, _ = 11), 11); _ASSERTEQUAL(clvalue, 0); int rvalue = 0; static_assert(tc::inplace_modifiable); _ASSERTEQUAL(tc_modified(tc_move(rvalue), _ = 11), 11); _ASSERTEQUAL(rvalue, 11); int const crvalue = 0; static_assert(!tc::inplace_modifiable); _ASSERTEQUAL(tc_modified(tc_move_always_even_const(crvalue), _ = 11), 11); _ASSERTEQUAL(crvalue, 0); int tmp_rvalue = 0; static_assert(tc::inplace_modifiable&&>); _ASSERTEQUAL(static_cast(tc_modified((tc::temporary(tmp_rvalue)), _ = 11)), 11); _ASSERTEQUAL(tmp_rvalue, 11); int tmp_crvalue = 0; static_assert(!tc::inplace_modifiable&&>); _ASSERTEQUAL(tc_modified((tc::temporary(tmp_crvalue)), _ = 11), 11); _ASSERTEQUAL(tmp_crvalue, 0); int tmp_lvalue_i = 0; tc::temporary tmp_lvalue(tmp_lvalue_i); static_assert(!tc::inplace_modifiable&>); _ASSERTEQUAL(tc_modified(tmp_lvalue, _ = 11), 11); _ASSERTEQUAL(tmp_lvalue_i, 0); int tmp_clvalue_i = 0; static_assert(!tc::inplace_modifiable&>); tc::temporary tmp_clvalue(tmp_clvalue_i); _ASSERTEQUAL(tc_modified(tmp_clvalue, _ = 11), 11); _ASSERTEQUAL(tmp_clvalue_i, 0); } ================================================ FILE: tc/base/move.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "fundamental.h" #include MODIFY_WARNINGS(((disable)(4521))) // '...' : multiple copy constructors specified (happens when two ctors, one with T const&, the other with T & parameter, are defined) namespace tc { ///////////////////////////// // remove_rvalue_reference namespace no_adl { template struct remove_rvalue_reference { using type=T; }; template struct remove_rvalue_reference { using type=T; }; } using no_adl::remove_rvalue_reference; template using remove_rvalue_reference_t=typename tc::remove_rvalue_reference::type; constexpr bool is_id_expression(char const* const strExpr) noexcept { for(auto it = strExpr; *it; ++it) if(!('A' <= *it && *it <= 'Z') && !('a' <= *it && *it <= 'z') && !('0' <= *it && *it <= '9') && *it != '_' && *it != ':') return false; return true; } } #define tc_is_id_expression(...) tc::is_id_expression(#__VA_ARGS__) ///////////////////////////////////////////// // safer variants of std::move namespace tc::move_detail { template constexpr decltype(auto) move(auto&& obj) noexcept { static_assert(!std::is_const>::value, "Cannot move out of const."); static_assert(!std::is_rvalue_reference::value, "Unnecessary move; already an rvalue."); static_assert(!std::is_lvalue_reference::value, "Are you sure you want to move out of an lvalue reference? Then use tc_move_always."); static_assert(bIsIdExpression, "Instead of `tc_move(obj.member)`, use `tc_move(obj).member`. Instead of `tc_move(ptr->member)`, use `tc_move_always(ptr->member)`."); return static_cast&&>(obj); } namespace no_adl { template struct move_if_owned final { static_assert(std::is_reference::value || bIsIdExpression, "Instead of `tc_move_if_owned(obj.member)`, use `tc_move_if_owned(obj).member`."); using type = Decltype&&; }; template struct const_forward final { static_assert(std::is_reference::value || bIsIdExpression, "Instead of `tc_const_forward(obj.member)`, use `tc_const_forward(obj).member`."); using type = std::conditional_t::value, Decltype, tc::remove_rvalue_reference_t const&&>; }; } } // T -> T&& (e.g. local variable) // T& -> error (e.g. lvalue reference argument) // T&& -> T&& (e.g. rvalue reference argument) #define tc_move(...) (static_cast(__VA_ARGS__))>(__VA_ARGS__)) // T -> T&& (e.g. local variable) // T& -> T& (e.g. lvalue reference argument) // T&& -> T&& (e.g. rvalue reference argument) #define tc_move_if_owned(...) (static_cast::type>(__VA_ARGS__)) // MSVC sometimes forgets a const when decltyping. #define tc_move_if_owned_msvc_workaround(Type, ...) (static_cast::type>(__VA_ARGS__)) // Implementing tc_move_always and tc_move_always_even_const as functions avoids workaround for MSVC. template [[nodiscard]] constexpr std::remove_reference_t&& tc_move_always(T&& t) noexcept { // same as std::move, but asserts on non-constness of argument static_assert(!std::is_const>::value, "Cannot move out of const."); return static_cast&&>(t); } template [[nodiscard]] constexpr std::remove_reference_t&& tc_move_always_even_const(T&& t) noexcept { return static_cast&&>(t); } // T -> T const&&, T& -> T&, T&& -> T const&& #define tc_const_forward(...) (static_cast::type>(__VA_ARGS__)) ================================================ FILE: tc/base/noncopyable.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "fundamental.h" namespace tc { namespace no_adl { // Derived classes can be moved and copied. struct TC_EMPTY_BASES copyable {}; // Derived classes can be moved, but not copied. struct TC_EMPTY_BASES noncopyable { protected: constexpr noncopyable() noexcept = default; noncopyable(noncopyable&&) noexcept = default; noncopyable& operator=(noncopyable&&) noexcept = default; }; // Derived classes cannot be moved or copied. struct TC_EMPTY_BASES nonmovable { nonmovable& operator=(nonmovable&&) noexcept = delete; }; } using no_adl::copyable; using no_adl::noncopyable; using no_adl::nonmovable; } ================================================ FILE: tc/base/ref.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include "as_lvalue.h" #include "../algorithm/for_each.h" namespace tc { namespace no_adl { /////////////////////////////////////////////////// // tc::function_ref union any_ref { // Any pointer to function can be converted to a pointer to a different function type. Calling the // function through a pointer to a different function type is undefined, but converting such pointer // back to pointer to the original function type yields the pointer to the original function. // https://en.cppreference.com/w/cpp/language/reinterpret_cast template explicit any_ref(RefT& ref) noexcept : m_pvRef(std::addressof(ref)) {} template requires std::is_function::value explicit any_ref(RefT& ref) noexcept : m_fpvRef(reinterpret_cast(std::addressof(ref))) {} template // may be const and/or volatile RefT& get_ref() const& noexcept { static_assert(!std::is_reference::value); if constexpr (std::is_function::value) { return *reinterpret_cast(m_fpvRef); } else { return *static_cast(tc::as_mutable_ptr(m_pvRef)); } } void const* m_pvRef; void (*m_fpvRef)(); }; template using type_erased_function_ptr = Ret(*)(tc::no_adl::any_ref, Args...) noexcept(bNoExcept); template struct make_type_erased_function_ptr final { tc::no_adl::type_erased_function_ptr operator()() const& noexcept { return [](tc::no_adl::any_ref anyref, Args... args) noexcept(bNoExcept) -> Ret { // implicit cast of stateless lambda to function pointer return tc_invoke_pack(anyref.get_ref(), tc_move_if_owned(args)); // MAYTHROW unless bNoExcept }; } }; template struct make_type_erased_function_ptr final { tc::no_adl::type_erased_function_ptr operator()() const& noexcept { return [](tc::no_adl::any_ref anyref, Args... args) noexcept(bNoExcept) { // implicit cast of stateless lambda to function pointer tc_invoke_pack(anyref.get_ref(), tc_move_if_owned(args)); // MAYTHROW unless bNoExcept }; } }; template struct make_type_erased_function_ptr final { tc::no_adl::type_erased_function_ptr operator()() const& noexcept { return [](tc::no_adl::any_ref anyref, Args... args) noexcept(bNoExcept) -> tc::break_or_continue { // implicit cast of stateless lambda to function pointer // Not tc::continue_if_not_break because it casts sink to const&. return tc_internal_continue_if_not_break(tc_invoke_pack(anyref.get_ref(), tc_move_if_owned(args))); // MAYTHROW unless bNoExcept }; } }; template struct function_ref_base { function_ref_base(function_ref_base const&) noexcept = default; Ret operator()(Args... args) const& noexcept(bNoExcept) { return m_pfuncTypeErased(m_anyref, tc_move_if_owned(args)...); // MAYTHROW unless bNoExcept } template requires (!tc::decayed_derived_from) && tc::invocable&, Args...> && ( std::convertible_to&>(), std::declval())), Ret> || std::is_same::value || std::is_void::value ) function_ref_base(Func&& func) noexcept : m_pfuncTypeErased(tc::no_adl::make_type_erased_function_ptr, Ret, Args...>{}()) , m_anyref(tc::as_lvalue(func)) { static_assert(!std::is_member_function_pointer::value, "Raw member functions are not supported (how would you call them?). Pass in a lambda or use std::mem_fn/tc_mem_fn instead."); static_assert(!std::is_pointer::value, "Pass in functions rather than function pointers."); // Checking the noexcept value of the function call is commented out because MAYTHROW is widely used in generic code such as for_each, range_adaptor... // static_assert(!bNoExcept || noexcept(std::declval&>()(std::declval()...))); } private: tc::no_adl::type_erased_function_ptr m_pfuncTypeErased; tc::no_adl::any_ref m_anyref; }; template struct function_ref; template struct function_ref : tc::no_adl::function_ref_base { using base_ = typename function_ref::function_ref_base; using base_::base_; }; template struct function_ref : tc::no_adl::function_ref_base { using base_ = typename function_ref::function_ref_base; using base_::base_; }; template struct any_range_ref { friend auto range_output_t_impl(any_range_ref const&) -> boost::mp11::mp_list; // declaration only template any_range_ref(Rng&& rng) noexcept : m_pfuncTypeErased( [](tc::no_adl::any_ref anyrefRng, tc::no_adl::function_ref fn) noexcept -> tc::break_or_continue { // implicit cast of stateless lambda to function pointer return tc::for_each(anyrefRng.get_ref>(), fn); } ) , m_anyrefRng(tc::as_lvalue(rng)) {} tc::break_or_continue operator()(tc::no_adl::function_ref fn) const& noexcept { return m_pfuncTypeErased(m_anyrefRng, fn); } private: tc::no_adl::type_erased_function_ptr> m_pfuncTypeErased; tc::no_adl::any_ref m_anyrefRng; }; } using no_adl::function_ref; using no_adl::any_range_ref; using no_adl::any_ref; } ================================================ FILE: tc/base/reference_or_value.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "renew.h" #include "tag_type.h" #include "casts.h" #include #include namespace tc { namespace no_adl { template< typename T, bool bBestAccess > struct value_holder_impl { T m_t; }; template< typename T> struct value_holder_impl { // Store mutable T to return mutable reference from best_access(). // best_access() is used to create an index object from an iterator // to m_t. add_index_interface::index needs a mutable iterator. // This is because of a deficiency in the STL: If there was a way to // get a mutable reference from a const_iterator *and* a mutable T, // the index type could always take the const_iterator instead. T mutable m_t; }; template< typename T, bool bBestAccess > struct reference_or_value: private value_holder_impl { static_assert( !std::is_void::value ); static_assert( !std::is_reference::value ); static_assert( !std::is_const::value ); static_assert( !std::is_volatile::value ); constexpr reference_or_value() = default; // m_t may be default-constructible constexpr reference_or_value(reference_or_value const&) = default; constexpr reference_or_value(reference_or_value&&) = default; template< typename Rhs> requires tc::safely_constructible_from constexpr reference_or_value( aggregate_tag_t, Rhs&& rhs ) noexcept : value_holder_impl{tc_move_if_owned(rhs)} {} // reference_or_value is trivially copy assignable if T is trivially copy assignable. constexpr reference_or_value& operator=(reference_or_value const& other) & requires std::is_trivially_copy_assignable::value = default; // T may be a proxy with reference behavior (e.g. pair): operator= must be non trivial and may not be equivalent to copy ctor. // operator=() with tc::renew for pointer semantics. #ifdef __clang__ // workaround clang bug on trivially copyable: https://github.com/llvm/llvm-project/issues/63352 template constexpr reference_or_value& operator=(reference_or_value const& other) & noexcept requires #else constexpr reference_or_value& operator=(reference_or_value const& other) & noexcept requires #endif (!std::is_trivially_copy_assignable::value) && std::is_copy_constructible::value { _ASSERTE(this != std::addressof(other)); tc::renew(this->m_t, other.m_t); return *this; } constexpr reference_or_value& operator=(reference_or_value&& other) & requires std::is_trivially_move_assignable::value = default; #ifdef __clang__ // workaround clang bug on trivially copyable: https://github.com/llvm/llvm-project/issues/63352 template constexpr reference_or_value& operator=(reference_or_value&& other) & noexcept requires #else constexpr reference_or_value& operator=(reference_or_value&& other) & noexcept requires #endif (!std::is_trivially_move_assignable::value) && std::is_move_constructible::value { _ASSERTE(this != std::addressof(other)); tc::renew(this->m_t, tc_move(other).m_t); return *this; } constexpr T& best_access() const& noexcept requires bBestAccess { // When declaring m_t non-mutable and using const_cast here be undefined behavior in code like: // reference_or_value _const_ foo; // foo.best_access(); // ? return this->m_t; } constexpr T* operator->() & noexcept { return std::addressof(this->m_t); } constexpr T const* operator->() const& noexcept { return std::addressof(this->m_t); } constexpr T& operator*() & noexcept { return this->m_t; } constexpr T const& operator*() const& noexcept { return this->m_t; } constexpr T&& operator*() && noexcept { return tc_move_always(*this).m_t; } constexpr T const&& operator*() const&& noexcept { return tc_move_always_even_const(tc::as_const(*this)).m_t; } }; template< typename T, bool bBestAccess > struct reference_or_value { private: T* m_pt; public: constexpr reference_or_value(aggregate_tag_t, T& t) noexcept : m_pt(std::addressof(t)) {} constexpr T& best_access() const& noexcept requires bBestAccess { return *m_pt; } constexpr T* operator->() const& noexcept { return m_pt; } constexpr T& operator*() const& noexcept { return *m_pt; } }; template< typename T, bool bBestAccess > struct reference_or_value { private: T* m_pt; public: constexpr reference_or_value(aggregate_tag_t, T&& t) noexcept : m_pt(std::addressof(t)) {} constexpr T&& best_access() const& noexcept requires bBestAccess { return tc_move_always(*m_pt); } constexpr T* operator->() const& noexcept { // no such thing as "pointer-to-rvalue" return m_pt; } constexpr T& operator*() const& noexcept { return *m_pt; } constexpr T&& operator*() const&& noexcept { return tc_move_always(*m_pt); } }; template< typename T > struct TC_EMPTY_BASES empty_value { static_assert( tc::empty_type ); constexpr empty_value() = default; constexpr empty_value(aggregate_tag_t, T) noexcept {} static constexpr T best_access() noexcept { return T(); } constexpr auto operator->() const& noexcept { struct pointer final { T m_t; constexpr T* operator->() && noexcept { return std::addressof(m_t); } }; return pointer(); } constexpr T operator*() const& noexcept { return T(); } }; } template using reference_or_value = std::conditional_t< tc::empty_type>>, no_adl::empty_value>>, no_adl::reference_or_value>, bBestAccess> >; template< bool bBestAccess=false, typename T > [[nodiscard]] constexpr auto make_reference_or_value(T&& t) return_ctor_noexcept( TC_FWD(tc::reference_or_value), (tc::aggregate_tag, tc_move_if_owned(t)) ) namespace no_adl { template< typename Func, typename... Args > struct stores_result_of final { private: tc::reference_or_value< decltype(std::declval()(std::declval()...)) > m_t; public: constexpr stores_result_of( Func&& func, Args&&... args ) MAYTHROW : m_t(aggregate_tag, tc_move_if_owned(func)(tc_move_if_owned(args)...)) {} constexpr auto get() const& noexcept ->decltype(auto) { return *m_t; } constexpr auto get() & noexcept ->decltype(auto) { return *m_t; } constexpr auto get() && noexcept ->decltype(auto) { return *tc_move(m_t); } template constexpr auto pass_to(FuncTo&& functo, ArgsTo&& ...args) const& noexcept ->decltype(auto) { return tc_move_if_owned(functo)(*m_t, tc_move_if_owned(args)...); } template constexpr auto pass_to(FuncTo&& functo, ArgsTo&& ...args) & noexcept ->decltype(auto) { return tc_move_if_owned(functo)(*m_t, tc_move_if_owned(args)...); } template constexpr auto pass_to(FuncTo&& functo, ArgsTo&& ...args) && noexcept ->decltype(auto) { return tc_move_if_owned(functo)(*tc_move(m_t), tc_move_if_owned(args)...); } }; template< typename Func, typename... Args > requires std::is_void< decltype(std::declval()(std::declval()...)) >::value struct stores_result_of final { constexpr stores_result_of( Func&& func, Args... args ) MAYTHROW { tc_move_if_owned(func)(static_cast(args)...); } static constexpr void get() noexcept {} template static constexpr auto pass_to(FuncTo&& functo, ArgsTo&& ...args) noexcept ->decltype(auto) { return tc_move_if_owned(functo)(tc_move_if_owned(args)...); } }; } template< typename Func, typename... Args > using stores_result_of=no_adl::stores_result_of; } ================================================ FILE: tc/base/reference_or_value.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "reference_or_value.h" #include "../tuple.h" #include static_assert(std::is_trivially_copyable>::value); static_assert(std::is_trivially_copy_assignable>::value); static_assert(std::is_trivially_move_assignable>::value); static_assert(!std::is_trivially_copy_assignable>::value); static_assert(!std::is_trivially_move_assignable>::value); static_assert(!std::is_trivially_copyable>>::value); static_assert(!std::is_trivially_copy_assignable>>::value); static_assert(!std::is_trivially_move_assignable>>::value); static_assert(std::is_trivially_copyable>::value); static_assert(std::is_trivially_copy_assignable>::value); static_assert(std::is_trivially_move_assignable>::value); static_assert(std::is_trivially_copyable>>::value); static_assert(std::is_trivially_copy_assignable>>::value); static_assert(std::is_trivially_move_assignable>>::value); ================================================ FILE: tc/base/renew.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include "explicit_cast_fwd.h" #include #include namespace tc { template constexpr void assert_most_derived(T const& t) noexcept { if constexpr( !std::is_final::value && std::is_polymorphic::value ) { // The runtime check is not necessary if T is final, and not available if T is not polymorphic. if( !std::is_constant_evaluated() ) { // type_info::operator== is not constexpr _ASSERTEQUAL(typeid(t), typeid(T)); } } } template< typename T > constexpr void dtor_static( T & t ) noexcept { // can call dtor on const&, but does not seem sensible #ifdef _MSC_VER // Cannot call destructor on uninitialized int during constant evaluation in MSVC. if constexpr( !std::is_trivially_destructible::value ) #endif { t.T::~T(); // Intentionally ignore dynamic type of T. } #if defined(_DEBUG) && defined(TC_PRIVATE) if( !std::is_constant_evaluated() ) { tc::fill_with_dead_pattern(t); } #endif } template requires (0==sizeof...(Args) && std::is_trivially_default_constructible::value/*value initialization*/) || tc::safely_constructible_from constexpr void ctor(T& t, Args&&... args) noexcept(std::is_nothrow_constructible::value) { MODIFY_WARNINGS_BEGIN(((disable)(4244))) // double to float is tc::safely_constructible_from but triggers warning/error 4244 with std::construct_at. std::construct_at(std::addressof(t), tc_move_if_owned(args)...); MODIFY_WARNINGS_END } template requires (!((0==sizeof...(Args) && std::is_trivially_default_constructible::value/*value initialization*/) || tc::safely_constructible_from)) && tc::explicit_castable_from constexpr void ctor(T& t, Args&&... args) noexcept(noexcept(tc::explicit_cast(std::declval()...))) { if constexpr( std::is_move_constructible::value ) { if( std::is_constant_evaluated() ) { tc::ctor(t, tc::explicit_cast(tc_move_if_owned(args)...)); return; } } // :: ensures that non-class scope operator new is used. // cast to void* ensures that built-in placement new is used (18.6.1.3). // not using std::construct_at to guarantee copy elision. ::new (static_cast(std::addressof(t))) T(tc::explicit_cast(tc_move_if_owned(args)...)); } namespace renew_detail { template constexpr void renew(T& t, Args&&... args) noexcept( // If t is not guaranteed default constructed in case tc::ctor throws, call std::terminate. noexcept(tc::ctor(t, std::declval()...)) || !std::is_trivially_default_constructible::value ) { tc::assert_most_derived(t); tc::dtor_static(t); tc::ctor(t, tc_move_if_owned(args)...); } } template< typename T > void renew_default(T& t) noexcept { static_assert( std::is_nothrow_default_constructible::value ); tc::assert_most_derived(t); tc::dtor_static(t); ::new (static_cast(std::addressof(t))) T; // :: ensures that non-class scope operator new is used, cast to void* ensures that built-in placement new is used (18.6.1.3) } template< typename T > // renew_value with non-empty argument list is useful in generic code constexpr void renew_value(T& t) return_MAYTHROW( renew_detail::renew(t) ) template< typename T, typename... Args > constexpr void renew(T& t, Args&&... args) noexcept(noexcept(renew_detail::renew(t, tc_move_if_owned(args)...))) { static_assert(!std::is_trivially_default_constructible::value || 0 < sizeof...(Args), "You must decide between renew_default and renew_value!"); renew_detail::renew(t, tc_move_if_owned(args)...); } template constexpr void assign_explicit_cast(Lhs& lhs, Rhs&&... rhs) MAYTHROW { lhs=tc::explicit_cast(tc_move_if_owned(rhs)...); } } #define ASSIGN_BY_RENEW_IMPL(Lhs, Rhs, /*SelfAssignCheck*/...) \ Lhs& operator=(Rhs rhs) & noexcept { \ /*static_assert( std::convertible_to< S, T >, "assignment must correspond to implicit construction" ); */ \ __VA_ARGS__ { \ tc::renew(*this, tc_move_if_owned(rhs)); \ } \ return *this; \ } #define COPY_SELF_ASSIGN_CHECK(Lhs, Rhs) \ static_assert(std::same_as> && std::is_lvalue_reference::value); \ if(std::addressof(rhs)!=this) // Self assignment check only need to be applied if std::remove_cvref_t and Lhs are the same type and Rhs is an lvalue reference, because // 1. for rvalue references passed to the C++ library, the caller must ensure that they can be treated as temporaries, e.g., that they don't alias. // - https://eel.is/c++draft/res.on.arguments#note-2 // - https://eel.is/c++draft/lib.types.movedfrom#2 // 2. values cannot alias. // 3. for same type copy assignment, rhs must be unchanged. https://en.cppreference.com/w/cpp/named_req/CopyAssignable // ASSIGN_BY_RENEW: assignment which has no danger of overlapping/self assignment. We must look case by case if Lhs and Rhs are different types and may overlap. #define ASSIGN_BY_RENEW(Lhs, Rhs) ASSIGN_BY_RENEW_IMPL(TC_FWD(Lhs), TC_FWD(Rhs), static_assert(!std::same_as> || !std::is_lvalue_reference::value);) // COPY_ASSIGN_BY_RENEW: same type copy assignment. check against self assignment is always needed because of dtor->ctor. #define COPY_ASSIGN_BY_RENEW(Lhs, Rhs) ASSIGN_BY_RENEW_IMPL(TC_FWD(Lhs), TC_FWD(Rhs), COPY_SELF_ASSIGN_CHECK(TC_FWD(Lhs), TC_FWD(Rhs))) #define ASSIGN_BY_SWAP(T) \ T& operator=( T other ) & noexcept { \ swap( *this, other ); /*not tc::swap, which may be implemented in terms of move, which would be circular*/ \ return *this; \ } ================================================ FILE: tc/base/return_decltype.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "temporary.h" namespace tc { #define return_MAYTHROW(...) noexcept(noexcept((__VA_ARGS__))) { \ return (__VA_ARGS__); \ } template using xvalue_decay_t = typename std::conditional_t::value || tc::instance_tn, tc::decay, std::type_identity >::type; // helper function to decay rvalues when decltype cannot be used because of lambdas in the expression template constexpr xvalue_decay_t lvalue_or_decay(T&& t) noexcept { return tc_move_if_owned(t); } //------------------------------------------------------------------------------------------------------------------------- // return_decltype: does not allow returning xvalues, safely handles tc::temporary namespace no_adl { template struct return_decltype final { static_assert(!std::is_rvalue_reference::value, "use return_decltype_allow_xvalue if you really want to return xvalue references, but be prepared to handle tc::temporary arguments"); using type = tc::return_temporary_t; }; } template using return_decltype_t = typename no_adl::return_decltype::type; #define return_decltype_MAYTHROW(...) noexcept(noexcept((__VA_ARGS__))) -> tc::return_decltype_t { \ return (__VA_ARGS__); \ } #define return_decltype_noexcept(...) noexcept -> tc::return_decltype_t { \ static_assert(noexcept((__VA_ARGS__)), "expression is not noexcept"); \ return (__VA_ARGS__); \ } // TODO C++20: When lambdas are allowed in unevaluated contexts, they will be allowed inside noexcept() expressions, and // any usage of return_decltype_NOEXCEPT(XYZ) should be replaced with return_decltype_noexcept(NOEXCEPT(XYZ)), and // we can remove NOEXCEPT_NO_LAMBDA #define return_decltype_NOEXCEPT(...) noexcept -> tc::return_decltype_t { \ return NOEXCEPT_NO_LAMBDA(__VA_ARGS__); \ } #define code_return_decltype(code, ...) -> tc::return_decltype_t { \ code \ return (__VA_ARGS__); \ } //------------------------------------------------------------------------------------------------------------------------- // return_decltype_allow_xvalue: allows returning xvalues but does not allow dangling temporaries #define return_decltype_allow_xvalue_MAYTHROW(...) noexcept(noexcept((__VA_ARGS__))) -> decltype((__VA_ARGS__)) { \ static_assert(!tc::dangling_temporary, "you need to use return_decltype_allow_xvalue_slow_MAYTHROW"); \ return (__VA_ARGS__); \ } #define return_decltype_allow_xvalue_noexcept(...) noexcept -> decltype((__VA_ARGS__)) { \ static_assert(!tc::dangling_temporary, "you need to use return_decltype_allow_xvalue_slow_noexcept"); \ static_assert(noexcept((__VA_ARGS__)), "expression is not noexcept"); \ return (__VA_ARGS__); \ } // We cannot have return_decltype_allow_xvalue_NOEXCEPT since xvalues cannot be passed through NOEXCEPT. #define code_return_decltype_allow_xvalue(code, ...) -> decltype((__VA_ARGS__)) { \ static_assert(!tc::dangling_temporary, "you need to use code_return_decltype_allow_xvalue_slow"); \ code \ return (__VA_ARGS__); \ } //------------------------------------------------------------------------------------------------------------------------- // return_decltype_allow_xvalue_slow: allows returning xvalues and safely handles dangling temporaries. // Only use it, when the static_assert tells you to. // (This cannot be folded into return_decltype_allow_xvalue due to a drastic compile-time increase.) #define return_decltype_allow_xvalue_slow_MAYTHROW(...) noexcept(noexcept((__VA_ARGS__))) -> tc::return_temporary_t { \ return (__VA_ARGS__); \ } #define return_decltype_allow_xvalue_slow_noexcept(...) noexcept -> tc::return_temporary_t { \ static_assert(noexcept((__VA_ARGS__)), "expression is not noexcept"); \ return (__VA_ARGS__); \ } // We cannot have return_decltype_allow_xvalue_slow_NOEXCEPT since xvalues cannot be passed through NOEXCEPT. #define code_return_decltype_allow_xvalue_slow(code, ...) -> tc::return_temporary_t { \ code \ return (__VA_ARGS__); \ } //------------------------------------------------------------------------------------------------------------------------- // return_ctor: always returns T as prvalue #define return_ctor_MAYTHROW(T, ...) noexcept(noexcept(T __VA_ARGS__)) -> T { \ return T __VA_ARGS__ ; \ } #define return_ctor_noexcept(T, ...) noexcept -> T { \ static_assert((noexcept(T __VA_ARGS__)), "expression is not noexcept"); \ return T __VA_ARGS__ ; \ } #define return_ctor_NOEXCEPT(T, ...) noexcept -> T { \ return NOEXCEPT_NO_LAMBDA(T __VA_ARGS__) ; \ } #define code_return_ctor(code, T, ...) -> T { \ code \ return T __VA_ARGS__ ; \ } //------------------------------------------------------------------------------------------------------------------------- // tc_auto_cref namespace no_adl { template struct auto_cref_impl final { STATICASSERTSAME(std::remove_cv_t, tc::decay_t); using type = std::remove_cv_t const; }; template struct auto_cref_impl final { STATICASSERTSAME(std::remove_cv_t, tc::decay_t); using type = std::remove_cv_t const; }; template struct auto_cref_impl> final { STATICASSERTSAME(std::remove_cv_t, tc::decay_t); using type = std::conditional_t<0 == Lifetime, std::remove_cv_t const, tc::temporary const>; // if 0 < Lifetime, the underlying temporary object lives longer than the current function }; template struct auto_cref_impl final { using type = T const&; }; } template using auto_cref_t = typename no_adl::auto_cref_impl>::type; template using auto_cref_return_t = std::remove_const_t>; #define tc_auto_cref( var, ... ) \ tc::auto_cref_t var = __VA_ARGS__ #define tc_auto_cref_return( var, ... ) \ tc::auto_cref_return_t var = __VA_ARGS__ } ================================================ FILE: tc/base/rvalue_property.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "return_decltype.h" #include "move.h" #define RVALUE_THIS_NAMED_OVERLOAD_CONST(METHOD, FUNC) \ template constexpr auto METHOD(Args&& ...args) const& return_decltype_allow_xvalue_MAYTHROW(FUNC(*this, tc_move_if_owned(args)...)) \ template constexpr auto METHOD(Args&& ...args) const&& return_decltype_allow_xvalue_MAYTHROW(FUNC(tc_move_always_even_const(*this), tc_move_if_owned(args)...)) // An underscore is appended to the static member function name because otherwise Visual Studio (as of version 19.15.26726) would trigger error C1202: // "recursive type or function dependency context too complex", while using the return_decltype_* macros for sfinae #define RVALUE_THIS_OVERLOAD_CONST(METHOD) \ RVALUE_THIS_NAMED_OVERLOAD_CONST(METHOD, BOOST_JOIN(METHOD,_)) #define RVALUE_THIS_NAMED_OVERLOAD_MOVABLE(METHOD, FUNC) \ RVALUE_THIS_NAMED_OVERLOAD_CONST(METHOD, FUNC) \ template constexpr auto METHOD(Args&& ...args) && return_decltype_allow_xvalue_MAYTHROW(FUNC(tc_move_always(*this), tc_move_if_owned(args)...)) #define RVALUE_THIS_OVERLOAD_MOVABLE(METHOD) \ RVALUE_THIS_NAMED_OVERLOAD_MOVABLE(METHOD, BOOST_JOIN(METHOD,_)) #define RVALUE_THIS_NAMED_OVERLOAD_MOVABLE_MUTABLE_REF(METHOD, FUNC) \ RVALUE_THIS_NAMED_OVERLOAD_MOVABLE(METHOD, FUNC) \ template constexpr auto METHOD(Args&& ...args) & return_decltype_allow_xvalue_MAYTHROW(FUNC(*this, tc_move_if_owned(args)...)) #define RVALUE_THIS_OVERLOAD_MOVABLE_MUTABLE_REF(METHOD) \ RVALUE_THIS_NAMED_OVERLOAD_MOVABLE_MUTABLE_REF(METHOD, BOOST_JOIN(METHOD,_)) ================================================ FILE: tc/base/rvalue_property.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "assert_defs.h" #include "rvalue_property.h" #include "type_traits.h" #include "../unittest.h" namespace { struct A final { template static decltype(auto) test_(Self&& self) noexcept { return (tc_move_if_owned(self).i); } int i; RVALUE_THIS_OVERLOAD_CONST(test); }; STATICASSERTSAME(decltype(std::declval().test()), int const&&); STATICASSERTSAME(decltype(std::declval().test()), int const&&); STATICASSERTSAME(decltype(std::declval().test()), int const&&); STATICASSERTSAME(decltype(std::declval().test()), int const&&); STATICASSERTSAME(decltype(std::declval().test()), int const&); STATICASSERTSAME(decltype(std::declval().test()), int const&); struct B final { template static decltype(auto) test_(Self&& self) noexcept { return (tc_move_if_owned(self).i); } int i; RVALUE_THIS_OVERLOAD_MOVABLE(test); }; STATICASSERTSAME(decltype(std::declval().test()), int&&); STATICASSERTSAME(decltype(std::declval().test()), int const&&); STATICASSERTSAME(decltype(std::declval().test()), int&&); STATICASSERTSAME(decltype(std::declval().test()), int const&&); STATICASSERTSAME(decltype(std::declval().test()), int const&); STATICASSERTSAME(decltype(std::declval().test()), int const&); struct C final { template static decltype(auto) test_(Self&& self) noexcept { return (tc_move_if_owned(self).i); } int i; RVALUE_THIS_OVERLOAD_MOVABLE_MUTABLE_REF(test); }; STATICASSERTSAME(decltype(std::declval().test()), int&&); STATICASSERTSAME(decltype(std::declval().test()), int const&&); STATICASSERTSAME(decltype(std::declval().test()), int&&); STATICASSERTSAME(decltype(std::declval().test()), int const&&); STATICASSERTSAME(decltype(std::declval().test()), int&); STATICASSERTSAME(decltype(std::declval().test()), int const&); } ================================================ FILE: tc/base/safe_comparison.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "type_traits.h" #include "casts.h" #include namespace tc { // By default, a comparison is safe; specialize to mark as unsafe. // (We can't delegate to safely_convertible_to, as we can't tell whether `T == U` is valid because of implicit conversions, // or because of an overloaded operator== that takes those types and handles them safely.) template constexpr bool safe_comparison = true; // Comparison between mixed integral types is only safe as long as neither is a char type and they have the same signedness. template requires (!std::same_as) constexpr bool safe_comparison = !tc::char_type && !tc::char_type && std::is_signed::value == std::is_signed::value; template concept safely_equality_comparable_with = std::equality_comparable_with && safe_comparison, tc::decay_t>; template concept safely_totally_ordered_with = std::totally_ordered_with && safe_comparison, tc::decay_t>; } ================================================ FILE: tc/base/safe_comparison.t.cpp ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #include "assert_defs.h" #include "safe_comparison.h" #include "../unittest.h" #include "../string/char.h" // Same type is safe. static_assert(tc::safe_comparison); static_assert(tc::safe_comparison); static_assert(tc::safe_comparison); // Mixed signedness is unsafe. static_assert(!tc::safe_comparison); static_assert(!tc::safe_comparison); // Mixed chars are unsafe. static_assert(!tc::safe_comparison); static_assert(!tc::safe_comparison); // chars and ints are unsafe. static_assert(!tc::safe_comparison); static_assert(!tc::safe_comparison); ================================================ FILE: tc/base/scope.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "type_traits.h" #include "noncopyable.h" #include "tag_type.h" #include "change.h" namespace tc { namespace no_adl { template struct scope_exit_impl final : private tc::nonmovable { constexpr explicit scope_exit_impl(T const& exitscope) noexcept : m_exitscope(exitscope) {} constexpr ~scope_exit_impl(){ m_exitscope(); } private: T const m_exitscope; }; struct make_scope_exit_impl { template [[nodiscard]] constexpr scope_exit_impl operator->*(T const& exitscope) && noexcept { return scope_exit_impl(exitscope); } }; } using no_adl::scope_exit_impl; using no_adl::make_scope_exit_impl; DEFINE_TAG_TYPE(scoped_assign_tag) namespace no_adl { template< typename T > struct scoped_restorer : private tc::nonmovable { static_assert(!std::is_array>::value); static_assert( std::is_lvalue_reference::value, "There may be use cases for non-lvalue types here. " "But this assert has to stay at least until https://connect.microsoft.com/VisualStudio/Feedback/Details/2117239 is fixed" ); protected: std::conditional_t< std::is_lvalue_reference::value, T, // regular reference std::remove_reference_t // proxy, store by value (and not as rvalue reference) > m_var; tc::decay_t m_valSaved; public: constexpr scoped_restorer( T&& var ) noexcept : m_var(tc_move_if_owned(var)), m_valSaved(VERIFYINITIALIZED(var)) {} constexpr scoped_restorer( T&& var, scoped_assign_tag_t ) noexcept : m_var(tc_move_if_owned(var)), m_valSaved(tc_move_always(VERIFYINITIALIZED(var))) {} constexpr ~scoped_restorer() { m_var=tc_move(m_valSaved); } // can be used to access/change saved value template constexpr scoped_restorer& operator=( T2&& t ) & noexcept { m_valSaved=tc_move_if_owned(t); return *this; } constexpr operator tc::decay_t const& () const& noexcept { return m_valSaved; } }; template scoped_restorer(T&&) -> scoped_restorer; } using no_adl::scoped_restorer; DEFINE_TAG_TYPE(scoped_change_tag) namespace no_adl { template< typename T > struct scoped_assigner : scoped_restorer { template constexpr explicit scoped_assigner( T&& var, T2&& val ) noexcept : scoped_restorer( tc_move_if_owned(var), scoped_assign_tag ) { this->m_var=tc_move_if_owned(val); } template constexpr explicit scoped_assigner( scoped_change_tag_t, T&& var, T2&& val ) noexcept : scoped_assigner( tc_move_if_owned(var), tc_move_if_owned(val) ) { _ASSERT(!(this->m_var == this->m_valSaved)); } using scoped_restorer::operator=; }; template scoped_assigner(T&&, T2&&) -> scoped_assigner; template scoped_assigner(scoped_change_tag_t, T&&, T2&&) -> scoped_assigner; } using no_adl::scoped_assigner; namespace no_adl { template< typename T > struct scoped_assigner_better : scoped_restorer { template constexpr explicit scoped_assigner_better( T& var, Val&& val, Better better ) noexcept : scoped_restorer( var ) { tc::assign_better(tc_move(better), var, tc_move_if_owned(val)); } }; template scoped_assigner_better(T&, Val&&, Better) -> scoped_assigner_better; } using no_adl::scoped_assigner_better; } // Note about brackets: // * in the decltype, var is parenthesized to add a reference. // * in the constructor, var and __VA_ARGS__ is not parenthesized to disallow use of the comma operator. // * we use braces to create the object to prevent a function declaration. // // We do not have a semicolon in the declaration to force the caller to add one. #define tc_restore_after_scope(var) tc::scoped_restorer< decltype((var)) > UNIQUE_IDENTIFIER{var} #define tc_scoped_assign(var, ...) tc::scoped_assigner< decltype((var)) > UNIQUE_IDENTIFIER{var, __VA_ARGS__} #define tc_scoped_change(var, ...) tc::scoped_assigner< decltype((var)) > UNIQUE_IDENTIFIER{tc::scoped_change_tag, var, __VA_ARGS__} #define tc_scoped_assign_max(var, ...) tc::scoped_assigner_better< decltype((var)) > UNIQUE_IDENTIFIER{var, __VA_ARGS__,tc::fn_greater()} #if _MSC_VER_FULL <= 190023026 #define tc_scoped_assign_for_baseclass_member(var, ...) tc::scoped_assigner< decltype(var)& > UNIQUE_IDENTIFIER{var, __VA_ARGS__} #define tc_restore_after_scope_for_baseclass_member(var) tc::scoped_restorer< decltype(var)& > UNIQUE_IDENTIFIER{var} #else #error "should be fixed: https://connect.microsoft.com/VisualStudio/Feedback/Details/2117239" #endif // tc_scope_exit must not throw in order to avoid double throws. If exceptions are needed, implement scope_success/scope_failure. #define tc_scope_exit auto UNIQUE_IDENTIFIER = tc::make_scope_exit_impl{} ->* [&]() noexcept -> void // the empty namespace ensures the macro is only used at file scope #define tc_file_scope_exit \ namespace {} \ static constinit auto UNIQUE_IDENTIFIER = tc::make_scope_exit_impl{} ->* []() noexcept -> void ================================================ FILE: tc/base/static_polymorphism.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "assert_defs.h" #include #include "tag_type.h" namespace tc { DEFINE_TAG_TYPE(unchecked_derived_cast_tag) } #define STATIC_VIRTUAL_METHOD_NAME( Name ) \ Name ## _ImplDoNotCallDirectly #define STATIC_VIRTUAL_DISPATCH_IMPL_NAME( Name ) \ Name ## _DispatchDoNotCallDirectly #define STATIC_VIRTUAL_FALLBACK_NAME( Name ) \ Name ## _FallbackDoNotCallDirectly #define STATIC_VIRTUAL_FORWARD_IMPL( Name, Decoration, ExpandCall, DerivedCast, MaybeUncheckedDerivedCastTagWithComma ) \ template \ constexpr auto Name(Args&&... args) Decoration return_decltype_allow_xvalue_MAYTHROW( \ ExpandCall( \ Name, \ TC_FWD(DerivedCast(static_cast(*MSVC_WORKAROUND_THIS))), \ MaybeUncheckedDerivedCastTagWithComma tc_move_if_owned(args)... \ ) \ ) #define STATIC_VIRTUAL_FORWARD_ALL_IMPL( Name, ExpandCall, DerivedCast, MaybeUncheckedDerivedCastTagWithComma ) \ STATIC_VIRTUAL_FORWARD_IMPL( Name, &, ExpandCall, DerivedCast, TC_FWD(MaybeUncheckedDerivedCastTagWithComma) ) \ STATIC_VIRTUAL_FORWARD_IMPL( Name, const&, ExpandCall, DerivedCast, TC_FWD(MaybeUncheckedDerivedCastTagWithComma) ) \ STATIC_VIRTUAL_FORWARD_IMPL( Name, &&, ExpandCall, DerivedCast, TC_FWD(MaybeUncheckedDerivedCastTagWithComma) ) \ STATIC_VIRTUAL_FORWARD_IMPL( Name, const&&, ExpandCall, DerivedCast, TC_FWD(MaybeUncheckedDerivedCastTagWithComma) ) #define STATIC_VIRTUAL_COMMON( Name ) \ using Name ## _derived_type = Derived; \ using Name ## _declaring_type = this_type; #define TC_STATIC_VIRTUAL_METHOD_CALL(Name, self, ...) \ self.STATIC_VIRTUAL_METHOD_NAME(Name)(__VA_ARGS__) #define STATIC_VIRTUAL( Name ) \ STATIC_VIRTUAL_COMMON( Name ) \ STATIC_VIRTUAL_FORWARD_ALL_IMPL( Name, TC_STATIC_VIRTUAL_METHOD_CALL, tc::derived_cast, /*MaybeUncheckedDerivedCastTagWithComma*/ ) #define UNCHECKED_STATIC_VIRTUAL( Name ) \ STATIC_VIRTUAL( Name ) \ STATIC_VIRTUAL_FORWARD_ALL_IMPL( Name, TC_STATIC_VIRTUAL_METHOD_CALL, tc::unchecked_derived_cast, TC_FWD(tc::unchecked_derived_cast_tag,) ) // force implementation as static method, using STATIC_FINAL_MOD(static, Name) #define STATIC_STATIC_VIRTUAL( Name ) \ using Name ## _derived_type = Derived; \ using Name ## _declaring_type = this_type; \ template \ static constexpr auto Name(Args_&& ...args) return_decltype_allow_xvalue_MAYTHROW( \ Derived_:: STATIC_VIRTUAL_METHOD_NAME(Name) (tc_move_if_owned(args)...) \ ) #define STATIC_VIRTUAL_WITH_DEFAULT_IMPL_MOD( Mod, Name ) \ STATIC_VIRTUAL( Name ) \ Mod \ auto STATIC_VIRTUAL_METHOD_NAME( Name ) #define STATIC_VIRTUAL_WITH_DEFAULT_IMPL( Name ) \ STATIC_VIRTUAL_WITH_DEFAULT_IMPL_MOD( BOOST_PP_EMPTY(), Name ) #define STATIC_FINAL_MOD_DECLARING(Mod, Declaring, Name) \ friend typename Declaring; \ static_assert( \ std::is_same< typename Declaring::Name ## _derived_type, this_type >::value, \ "The class implementing the final static virtual method must be the Derived type of the class declaring the method." \ ); \ Mod \ auto STATIC_VIRTUAL_METHOD_NAME( Name ) #define STATIC_FINAL_DECLARING(Declaring, Name) \ STATIC_FINAL_MOD_DECLARING(BOOST_PP_EMPTY(), TC_FWD(Declaring), Name) #define STATIC_FINAL_MOD(Mod, Name) \ STATIC_FINAL_MOD_DECLARING(TC_FWD(Mod), this_type::Name ## _declaring_type, Name) #define STATIC_FINAL(Name) \ STATIC_FINAL_MOD(BOOST_PP_EMPTY(), Name) #define STATIC_OVERRIDE_MOD_DECLARING_BASE(Declaring, Name, ...) \ friend typename Declaring; \ __VA_ARGS__ \ auto STATIC_VIRTUAL_METHOD_NAME( Name ) #define STATIC_OVERRIDE_MOD_DECLARING(Mod, Declaring, Name) \ STATIC_OVERRIDE_MOD_DECLARING_BASE(TC_FWD(Declaring), Name, \ static_assert( \ std::is_same::value, \ "The Derived type of the class implementing a non-final static virtual method must be the same as the one of the class declaring the method." \ ); \ Mod \ ) #define STATIC_OVERRIDE_DECLARING(Declaring, Name) \ STATIC_OVERRIDE_MOD_DECLARING(BOOST_PP_EMPTY(), TC_FWD(Declaring), Name) #define STATIC_OVERRIDE_MOD(Mod, Name) \ STATIC_OVERRIDE_MOD_DECLARING(TC_FWD(Mod), this_type::Name ## _declaring_type, Name) #define STATIC_OVERRIDE( Name ) \ STATIC_OVERRIDE_MOD( BOOST_PP_EMPTY(), Name ) #define TC_STATIC_VIRTUAL_DISPATCH_CALL(Name, self, ...) \ STATIC_VIRTUAL_DISPATCH_IMPL_NAME(Name)(self, __VA_ARGS__) #define STATIC_VIRTUAL_WITH_FALLBACK_MOD(Mod, Name) \ STATIC_VIRTUAL_COMMON( Name ) \ template \ static constexpr auto STATIC_VIRTUAL_DISPATCH_IMPL_NAME(Name)(Derived_&& derived, Args&&... args) return_decltype_allow_xvalue_MAYTHROW( \ tc_move_if_owned(derived).STATIC_VIRTUAL_METHOD_NAME(Name)(tc_move_if_owned(args)...) \ ) \ template().STATIC_VIRTUAL_METHOD_NAME(Name)( std::declval()... ); } ) \ static constexpr auto STATIC_VIRTUAL_DISPATCH_IMPL_NAME(Name)(Derived_&& derived, Args&&... args) return_decltype_allow_xvalue_MAYTHROW( \ tc_move_if_owned(derived).STATIC_VIRTUAL_FALLBACK_NAME(Name)(tc_move_if_owned(args)...) \ ) \ STATIC_VIRTUAL_FORWARD_ALL_IMPL( Name, TC_STATIC_VIRTUAL_DISPATCH_CALL, tc::derived_cast, /*MaybeUncheckedDerivedCastTagWithComma*/ ) \ Mod \ auto STATIC_VIRTUAL_FALLBACK_NAME(Name) #define STATIC_VIRTUAL_WITH_FALLBACK(Name) \ STATIC_VIRTUAL_WITH_FALLBACK_MOD( BOOST_PP_EMPTY(), Name ) ================================================ FILE: tc/base/string_template_param.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "type_traits_fwd.h" namespace tc { namespace literal_range_adl { template struct literal_range; } using literal_range_adl::literal_range; namespace no_adl { template requires tc::char_like && (!std::is_volatile::value) struct string_template_param final { consteval string_template_param(Char const (&str)[N]) noexcept { for(int i=0; i consteval string_template_param(literal_range) noexcept { static_assert(sizeof...(Cs) + 1 == N); std::size_t idx = 0; ((m_str[idx++] = Cs), ...); // avoid using library functions to prevent circular dependency on _ASSERT m_str[idx] = Char(); } constexpr auto begin() const& noexcept { return &m_str[0]; } constexpr auto end() const& noexcept { return &m_str[N-1]; } static constexpr auto size() noexcept { return tc::least_uint_constant{}; } constexpr operator auto const&() const& noexcept { return m_str; } constexpr Char operator[](std::size_t idx) const& noexcept { return m_str[idx]; } Char m_str[N]; }; template string_template_param(literal_range) -> string_template_param; } using no_adl::string_template_param; } ================================================ FILE: tc/base/tag_type.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include #include #include #include #define DERIVE_FROM_BASE_TAG(...) : __VA_ARGS__##_t #define DEFINE_TAG_TYPE_STRUCT(tag_name, base_tag_name, derivable) \ struct tag_name##_t derivable BOOST_PP_EXPR_IF(BOOST_PP_NOT(BOOST_PP_IS_EMPTY(base_tag_name)), DERIVE_FROM_BASE_TAG(base_tag_name)) { \ constexpr explicit tag_name##_t() noexcept = default; /* to avoid implicit initialization from {} */ \ using is_tag = void; \ }; #define DEFINE_TAG_TYPE_VAR(tag_name, specifier) \ specifier constexpr auto tag_name = tag_name##_t(); #define DEFINE_TAG_TYPE_BASE(tag_name, base_tag_name, derivable) \ namespace no_adl { \ DEFINE_TAG_TYPE_STRUCT(tag_name, base_tag_name, derivable) \ } \ using no_adl::tag_name##_t; \ DEFINE_TAG_TYPE_VAR(tag_name, inline) #define DEFINE_TAG_TYPE(tag_name) \ DEFINE_TAG_TYPE_BASE(tag_name, , final) #define DEFINE_NESTED_TAG_TYPE(tag_name) \ DEFINE_TAG_TYPE_STRUCT(tag_name, , final) \ DEFINE_TAG_TYPE_VAR(tag_name, static) #define DEFINE_ADL_TAG_TYPE(tag_name) \ DEFINE_TAG_TYPE_STRUCT(tag_name, , final) \ DEFINE_TAG_TYPE_VAR(tag_name, inline) #define DEFINE_TEMPLATE_TAG_TYPE(tag_name) \ namespace no_adl { \ template \ DEFINE_TAG_TYPE_STRUCT(tag_name, , final) \ } \ using no_adl::tag_name##_t; \ template \ inline constexpr auto tag_name = tag_name##_t(); namespace tc { DEFINE_TAG_TYPE(aggregate_tag) // tag to distinguish constructors that aggregate their single argument from templated copy constructors DEFINE_TAG_TYPE(func_tag) DEFINE_TEMPLATE_TAG_TYPE(type_tag) // tc_define_fn(func) always defines a function void func(define_fn_dummy_t) // If that function did not exist, -> decltype( func(...) ) would not be // a valid statement and clang complains about that. DEFINE_TAG_TYPE(define_fn_dummy) template concept tag = requires { typename std::remove_cv_t::is_tag; }; } ================================================ FILE: tc/base/template_func.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "tag_type.h" #include "type_list.h" #define DECLARE_TMPL_FUNC_WITH_CUSTOMIZATIONS(name) \ namespace name ## _adl { \ struct adl_tag_t; \ } \ namespace no_adl { \ struct fn_ ## name; \ } \ extern inline no_adl::fn_ ## name const name; // define a template function with possible customization overloads // 1. overloads for external types: highest priority; customization name_impl(adl_tag_prio_t, ...) is defined in name_adl namespace with adl_tag_prio_t as 1st parameter // 2. overloads for our classes: second highest priority; customization name_impl(...) is defined in ADL namespace (prefer friend function) // 3. overloads for external types: third highest priority; customization name_impl(adl_tag_t, ...) is defined in name_adl namespace with adl_tag_t as 1st parameter // 4. default implementation(s): lowest priority; name_impl(...) is implemented in name_default namespace before this macro #define DEFINE_TMPL_FUNC_WITH_CUSTOMIZATIONS(name) \ namespace name ## _default { \ void name ## _impl(tc::define_fn_dummy_t) noexcept; \ } \ namespace name ## _adl { \ DEFINE_ADL_TAG_TYPE(adl_tag) \ DEFINE_ADL_TAG_TYPE(adl_tag_prio) \ } \ namespace name ## _detail { \ template \ concept has_adl_tag_prio_ ## name ## _impl = requires { name ## _impl(name ## _adl::adl_tag_prio, std::declval()...); }; \ template \ concept has_adl_ ## name ## _impl = requires { name ## _impl(std::declval()...); }; \ template \ concept has_adl_tag_ ## name ## _impl = requires { name ## _impl(name ## _adl::adl_tag, std::declval()...); }; \ template \ concept has_default_ ## name ## _impl = requires { name ## _default:: name ## _impl(std::declval()...); }; \ } \ \ namespace no_adl { \ struct [[nodiscard]] TC_EMPTY_BASES fn_ ## name { \ private: \ template \ static constexpr bool NoExcept() noexcept { \ if constexpr(name ## _detail::has_adl_tag_prio_ ## name ## _impl) { \ return noexcept(name ## _impl(name ## _adl::adl_tag_prio, std::declval()...)); \ } else if constexpr (name ## _detail::has_adl_ ## name ## _impl) { \ return noexcept(name ## _impl(std::declval()...)); \ } else if constexpr(name ## _detail::has_adl_tag_ ## name ## _impl) { \ return noexcept(name ## _impl(name ## _adl::adl_tag, std::declval()...)); \ } else { static_assert(name ## _detail::has_default_ ## name ## _impl); \ return noexcept(name ## _default:: name ## _impl(std::declval()...)); \ } \ } \ public: \ using is_transparent=void; \ template requires \ name ## _detail::has_adl_tag_prio_ ## name ## _impl || \ name ## _detail::has_adl_ ## name ## _impl || \ name ## _detail::has_adl_tag_ ## name ## _impl || \ name ## _detail::has_default_ ## name ## _impl \ constexpr decltype(auto) operator()(Args&& ... args) const& noexcept( NoExcept() ) { \ if constexpr(name ## _detail::has_adl_tag_prio_ ## name ## _impl) { \ return name ## _impl(name ## _adl::adl_tag_prio, tc_move_if_owned(args)...); \ } else if constexpr (name ## _detail::has_adl_ ## name ## _impl) { \ return name ## _impl(tc_move_if_owned(args)...); \ } else if constexpr(name ## _detail::has_adl_tag_ ## name ## _impl) { \ return name ## _impl(name ## _adl::adl_tag, tc_move_if_owned(args)...); \ } else { static_assert(name ## _detail::has_default_ ## name ## _impl); \ return name ## _default:: name ## _impl(tc_move_if_owned(args)...); \ } \ } \ }; \ } \ inline no_adl::fn_ ## name constexpr name; ================================================ FILE: tc/base/temporary.h ================================================ // think-cell public library // // Copyright (C) think-cell Software GmbH // // Distributed under 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 #pragma once #include "as_lvalue.h" #include "move.h" #include "type_traits_fwd.h" namespace tc { //------------------------------------------------------------------------------------------------------------------------- // `tc::temporary` wraps a temporary object that was created Lifetime functions above in the callstack from a prvalue. // Like a reference, `tc::temporary const` is equivalent to `tc::temporary`. // Like an rvalue reference, `tc::temporary cv2&` is equivalent to `T cv&` and `tc::temporary cv2&&` is equivalent to `tc::temporay`. namespace temporary_adl { template struct temporary { static_assert(std::is_object::value); static_assert(!tc::instance_tn); private: T&& m_ref; template friend struct temporary; public: template requires std::same_as, std::remove_cv_t> && std::is_convertible*, T*>::value constexpr explicit(std::is_lvalue_reference::value && !std::is_const::value) temporary(U&& ref) noexcept : m_ref(tc_move_always_even_const(ref)) {} template requires std::same_as, std::remove_cv_t> && std::is_convertible*, T*>::value constexpr explicit(OtherLifetime < Lifetime) temporary(temporary const& other) noexcept : m_ref(tc_move_always_even_const(other.m_ref)) {} constexpr operator T&() const& noexcept { return m_ref; } constexpr operator T&&() && noexcept { // overload needed for clang: https://godbolt.org/z/9sv9YsMsc return tc_move_always_even_const(m_ref); } constexpr operator T&&() const&& noexcept { return tc_move_always_even_const(m_ref); } }; } using temporary_adl::temporary; namespace no_adl { template struct remove_temporary_impl { using type = T; }; template requires tc::instance_tn struct remove_temporary_impl { using type = boost::mp11::mp_first::arguments>; }; } template using remove_ref_temporary_t = typename no_adl::remove_temporary_impl>::type; namespace no_adl { // We use a nested structure to have a single top-level instantiation for all traits. // E.g. for a non-temporary type, `tc::increment_lifetime_t` and `tc::decrement_lifetime_t` only cause one type instanitation. // (The temporary traits are instantiated for many different types and can easily cause compile times to explode.) template struct temporary_trait_impl { template