/* * Copyright (c) 2019-2025 The IceCream-Cpp Developers. See the AUTHORS file at the * top-level directory of this distribution and at * https://github.com/renatoGarcia/icecream-cpp * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef ICECREAM_HPP_INCLUDED #define ICECREAM_HPP_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_MSC_VER) // Disable unharmful MSVC warnings within this header, so that we can build it without // errors using "/Wall /WX" flags #pragma warning(push) // C4127: conditional expression is constant // C4355: 'this' used in base member initializer list // C4514: unreferenced inline function has been removed // C4623: default constructor was implicitly defined as deleted // C4626: assignment operator was implicitly defined as deleted // C4840: bytes padding added after construct 'member_name' // C4866: compiler may not enforce left-to-right evaluation order for call // C4868: compiler may not enforce left-to-right evaluation order in braced initializer list // C5027: move assignment operator was implicitly defined as deleted // C5045: Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified // These both are triggered in `Variant` class, due to the unamed union: // C4582: constructor is not implicitly called // C4583: destructor is not implicitly called #pragma warning(disable: 4127 4355 4514 4623 4626 4820 4866 4868 5027 5045 4582 4583) #endif #if (!defined(__APPLE__) && (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 15000)) #define ICECREAM_UCHAR_HEADER #include #endif #if defined(__cpp_lib_optional) || (__cplusplus >= 201703L) #define ICECREAM_OPTIONAL_HEADER #include #endif #if defined(__cpp_lib_variant) || (__cplusplus >= 201703L) #define ICECREAM_VARIANT_HEADER #include #endif #if defined(__cpp_lib_string_view) || (__cplusplus >= 201703L) #define ICECREAM_STRING_VIEW_HEADER #include #endif #if defined(__has_builtin) && defined(__clang__) #if __has_builtin(__builtin_dump_struct) && __clang_major__ >= 15 #define ICECREAM_DUMP_STRUCT_CLANG #endif #endif #if defined(__has_include) && __has_include() #include #if defined(__cpp_lib_ranges) #define ICECREAM_LIB_RANGES #endif #endif #if !defined(__APPLE__) && defined(__has_include) && __has_include() #include #if defined(__cpp_lib_source_location) #define ICECREAM_SOURCE_LOCATION #endif #endif #if defined(__has_include) && __has_include() #include #if defined(__cpp_lib_format) || (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 170000 && __cplusplus >= 202002L) // libc++ just defines the '__cpp_lib_format' macro start from version 19. However // from version 17 it already implemens all functionalities that we need. #define ICECREAM_STL_FORMAT #endif #if defined(__cpp_lib_format_ranges) #define ICECREAM_STL_FORMAT_RANGES #endif #endif // ICECREAM_FMT is the macro which can be defined to enable {fmt} support regardless of // any other condition. // // The "defined()" test for FMT_VERSION macro is an attempt to check if any {fmt} header // was "#included" before the including of this Icecream-cpp header. If so, the {fmt} // support will be enabled. #if defined(ICECREAM_FMT) || defined(FMT_VERSION) // All the {fmt} headers from supoorted versions (5.0 and newer) will directly or // indirectly include a source file where FMT_VERSION is defined. #include #define ICECREAM_FMT_ENABLED #endif // ICECREAM_RANGE_V3 is the macro which can be defined to enable range-v3 support // regardless of any other condition. // // The "defined()" test for RANGES_V3_DETAIL_CONFIG_HPP macro is an attempt to check if // any range-v3 header was "#included" before the including of this icecream-cpp header. // If so, the range-v3 support will be enabled. #if defined(ICECREAM_RANGE_V3) || defined(RANGES_V3_DETAIL_CONFIG_HPP) // The RANGES_V3_DETAIL_CONFIG_HPP is the include guard of the header // . This header is present in all range-v3 releases, and // is direct or indirectly included by all other range-v3 headers, excepting a few // ones like . Because of that, checking by the definition of // RANGES_V3_DETAIL_CONFIG_HPP is a good way to determine if any range-v3 header was // previously included. #define ICECREAM_RANGE_V3_ENABLED #include #include namespace icecream { namespace detail { #if RANGE_V3_VERSION <= 500 namespace rv3v = ::ranges::view; #else namespace rv3v = ::ranges::views; #endif }} #endif #define ICECREAM_DEV_HASH "$Format:%H$" #if defined(__GNUC__) #define ICECREAM_FUNCTION __PRETTY_FUNCTION__ #elif defined(_MSC_VER) #define ICECREAM_FUNCTION __FUNCSIG__ #else #define ICECREAM_FUNCTION __func__ #endif // Used to force MSVC to unpack __VA_ARGS__ #define ICECREAM_EXPAND(X) X // Returns the number of args of a callable on IC_A macro. To be able to measure // 0 arguments, the first name of the input __VA_ARGS__ must be the callable // itself. In other words, this macro will return one less than the size of its // inputs. #define ICECREAM_ARGS_SIZE(...) ICECREAM_EXPAND(ICECREAM_ARGS_SIZE_( \ __VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, \ 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, \ 6, 5, 4, 3, 2, 1, 0)) #define ICECREAM_ARGS_SIZE_( \ _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \ _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, \ _26, _27, _28, _29, _30, _31, _32, N, ...) N #define ICECREAM_UNPACK_0 #define ICECREAM_UNPACK_1 std::get<0>(std::move(ret_tuple)) #define ICECREAM_UNPACK_2 ICECREAM_UNPACK_1, std::get<1>(std::move(ret_tuple)) #define ICECREAM_UNPACK_3 ICECREAM_UNPACK_2, std::get<2>(std::move(ret_tuple)) #define ICECREAM_UNPACK_4 ICECREAM_UNPACK_3, std::get<3>(std::move(ret_tuple)) #define ICECREAM_UNPACK_5 ICECREAM_UNPACK_4, std::get<4>(std::move(ret_tuple)) #define ICECREAM_UNPACK_6 ICECREAM_UNPACK_5, std::get<5>(std::move(ret_tuple)) #define ICECREAM_UNPACK_7 ICECREAM_UNPACK_6, std::get<6>(std::move(ret_tuple)) #define ICECREAM_UNPACK_8 ICECREAM_UNPACK_7, std::get<7>(std::move(ret_tuple)) #define ICECREAM_UNPACK_9 ICECREAM_UNPACK_8, std::get<8>(std::move(ret_tuple)) #define ICECREAM_UNPACK_10 ICECREAM_UNPACK_9, std::get<9>(std::move(ret_tuple)) #define ICECREAM_UNPACK_11 ICECREAM_UNPACK_10, std::get<10>(std::move(ret_tuple)) #define ICECREAM_UNPACK_12 ICECREAM_UNPACK_11, std::get<11>(std::move(ret_tuple)) #define ICECREAM_UNPACK_13 ICECREAM_UNPACK_12, std::get<12>(std::move(ret_tuple)) #define ICECREAM_UNPACK_14 ICECREAM_UNPACK_13, std::get<13>(std::move(ret_tuple)) #define ICECREAM_UNPACK_15 ICECREAM_UNPACK_14, std::get<14>(std::move(ret_tuple)) #define ICECREAM_UNPACK_16 ICECREAM_UNPACK_15, std::get<15>(std::move(ret_tuple)) #define ICECREAM_UNPACK_17 ICECREAM_UNPACK_16, std::get<16>(std::move(ret_tuple)) #define ICECREAM_UNPACK_18 ICECREAM_UNPACK_17, std::get<17>(std::move(ret_tuple)) #define ICECREAM_UNPACK_19 ICECREAM_UNPACK_18, std::get<18>(std::move(ret_tuple)) #define ICECREAM_UNPACK_20 ICECREAM_UNPACK_19, std::get<19>(std::move(ret_tuple)) #define ICECREAM_UNPACK_21 ICECREAM_UNPACK_20, std::get<20>(std::move(ret_tuple)) #define ICECREAM_UNPACK_22 ICECREAM_UNPACK_21, std::get<21>(std::move(ret_tuple)) #define ICECREAM_UNPACK_23 ICECREAM_UNPACK_22, std::get<22>(std::move(ret_tuple)) #define ICECREAM_UNPACK_24 ICECREAM_UNPACK_23, std::get<23>(std::move(ret_tuple)) #define ICECREAM_UNPACK_25 ICECREAM_UNPACK_24, std::get<24>(std::move(ret_tuple)) #define ICECREAM_UNPACK_26 ICECREAM_UNPACK_25, std::get<25>(std::move(ret_tuple)) #define ICECREAM_UNPACK_27 ICECREAM_UNPACK_26, std::get<26>(std::move(ret_tuple)) #define ICECREAM_UNPACK_28 ICECREAM_UNPACK_27, std::get<27>(std::move(ret_tuple)) #define ICECREAM_UNPACK_29 ICECREAM_UNPACK_28, std::get<28>(std::move(ret_tuple)) #define ICECREAM_UNPACK_30 ICECREAM_UNPACK_29, std::get<29>(std::move(ret_tuple)) #define ICECREAM_UNPACK_31 ICECREAM_UNPACK_30, std::get<30>(std::move(ret_tuple)) #define ICECREAM_UNPACK_32 ICECREAM_UNPACK_31, std::get<31>(std::move(ret_tuple)) #define ICECREAM_SCOPE_VARS \ auto const* const icecream_parent_config_5f803a3bcdb4 = &icecream_private_config_5f803a3bcdb4; \ ::icecream::detail::Config_ icecream_private_config_5f803a3bcdb4(icecream_parent_config_5f803a3bcdb4); \ ::icecream::Config& icecream_public_config_5f803a3bcdb4 = icecream_private_config_5f803a3bcdb4; #define ICECREAM_APPLY_(fmt, argument_names, N, callable, ...) \ [&]() \ { \ auto ret_tuple = ICECREAM_DISPATCH( \ true, fmt, argument_names \ ).tuple_run(__VA_ARGS__); \ (void) ret_tuple; \ return callable(ICECREAM_UNPACK_##N); \ }() #define ICECREAM_APPLY_0(callable) \ [&]() \ { \ ICECREAM_DISPATCH(true, "", #callable).tuple_run(); \ return callable(); \ }() #define ICECREAM_APPLY(fmt, argument_names, N, ...) \ ICECREAM_EXPAND(ICECREAM_APPLY_(fmt, argument_names, N, __VA_ARGS__)) #define ICECREAM_DISPATCH(is_ic_apply, fmt, argument_names) \ ::icecream::detail::Dispatcher{ \ is_ic_apply, icecream_private_config_5f803a3bcdb4, __FILE__, __LINE__, ICECREAM_FUNCTION, fmt, argument_names \ } #if defined(ICECREAM_LONG_NAME) #define ICECREAM(...) ICECREAM_DISPATCH(false, "", #__VA_ARGS__).unary_run(__VA_ARGS__) #define ICECREAM0() ICECREAM_DISPATCH(false, "", "").unary_run() #define ICECREAM_F(fmt, ...) ICECREAM_DISPATCH(false, fmt, #__VA_ARGS__).unary_run(__VA_ARGS__) #define ICECREAM_A(...) ICECREAM_APPLY("", #__VA_ARGS__, ICECREAM_ARGS_SIZE(__VA_ARGS__), __VA_ARGS__) #define ICECREAM_A0(callable) ICECREAM_APPLY_0(callable) #define ICECREAM_FA(fmt, ...) ICECREAM_APPLY(fmt, #__VA_ARGS__, ICECREAM_ARGS_SIZE(__VA_ARGS__), __VA_ARGS__) #define ICECREAM_(...) ::icecream::detail::make_formatting_argument(__VA_ARGS__) #define ICECREAM_V(...) ::icecream::detail::IC_V_(__VA_ARGS__).complete(icecream_private_config_5f803a3bcdb4, __LINE__, __FILE__, ICECREAM_FUNCTION) #define ICECREAM_FV(...) ::icecream::detail::IC_FV_(__VA_ARGS__).complete(icecream_private_config_5f803a3bcdb4, __LINE__, __FILE__, ICECREAM_FUNCTION) #if defined(__GNUC__) // Disable global and outer scope name shadowing warnings // // GCC at version 6 and older has a bug when processing a `_Pragma` directive within // macro expansions. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69126 // so this won't silence the -Wshadow warning. #define ICECREAM_CONFIG_SCOPE() \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wshadow\"") \ ICECREAM_SCOPE_VARS \ _Pragma("GCC diagnostic pop") #elif defined(_MSC_VER) // Disable global and outer scope name shadowing warnings #define ICECREAM_CONFIG_SCOPE() \ __pragma(warning(push)) \ __pragma(warning(disable: 4456 4459)) \ ICECREAM_SCOPE_VARS \ __pragma(warning(pop)) #else #define ICECREAM_CONFIG_SCOPE() \ ICECREAM_SCOPE_VARS #endif #define ICECREAM_CONFIG icecream_public_config_5f803a3bcdb4 #else #define IC(...) ICECREAM_DISPATCH(false, "", #__VA_ARGS__).unary_run(__VA_ARGS__) #define IC0() ICECREAM_DISPATCH(false, "", "").unary_run() #define IC_F(fmt, ...) ICECREAM_DISPATCH(false, fmt, #__VA_ARGS__).unary_run(__VA_ARGS__) #define IC_A(...) ICECREAM_APPLY("", #__VA_ARGS__, ICECREAM_ARGS_SIZE(__VA_ARGS__), __VA_ARGS__) #define IC_A0(callable) ICECREAM_APPLY_0(callable) #define IC_FA(fmt, ...) ICECREAM_APPLY(fmt, #__VA_ARGS__, ICECREAM_ARGS_SIZE(__VA_ARGS__), __VA_ARGS__) #define IC_(...) ::icecream::detail::make_formatting_argument(__VA_ARGS__) #define IC_V(...) ::icecream::detail::IC_V_(__VA_ARGS__).complete(icecream_private_config_5f803a3bcdb4, __LINE__, __FILE__, ICECREAM_FUNCTION) #define IC_FV(...) ::icecream::detail::IC_FV_(__VA_ARGS__).complete(icecream_private_config_5f803a3bcdb4, __LINE__, __FILE__, ICECREAM_FUNCTION) #if defined(__GNUC__) // Disable global and outer scope name shadowing warnings // // GCC at version 6 and older has a bug when processing a `_Pragma` directive within // macro expansions. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69126 // so this won't silence the -Wshadow warning. #define IC_CONFIG_SCOPE() \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wshadow\"") \ ICECREAM_SCOPE_VARS \ _Pragma("GCC diagnostic pop") #elif defined(_MSC_VER) // Disable global and outer scope name shadowing warnings #define IC_CONFIG_SCOPE() \ __pragma(warning(push)) \ __pragma(warning(disable: 4456 4459)) \ ICECREAM_SCOPE_VARS \ __pragma(warning(pop)) #else #define IC_CONFIG_SCOPE() \ ICECREAM_SCOPE_VARS #endif #define IC_CONFIG icecream_public_config_5f803a3bcdb4 #endif #define ICECREAM_UNREACHABLE assert(((void)"Should not reach here. Please report the bug", false)) namespace boost { class exception; // Forward declare this internal function because boost::diagnostic_information has a // default argument, and if this icecream.hpp header is included before the boost // exception headers it will trigger a compile error: redeclaration of ‘template std::string boost::diagnostic_information(const T&, bool)’ may not have default // arguments namespace exception_detail { std::string diagnostic_information_impl( boost::exception const*, std::exception const*, bool, bool ); } template class scoped_ptr; template class weak_ptr; namespace variant2 { template class variant; template struct variant_size; template constexpr auto visit(Visitor&& vis, Variant&& var) -> R; } } namespace icecream{ namespace detail { // To allow ADL to find custom `begin`, `end`, `size`, and `to_string` function overloads using std::begin; using std::end; #if defined(__cpp_lib_nonmember_container_access) using std::size; #endif using std::to_string; // -------------------------------------------------- is_instantiation // Checks if a type T (like std::pair) is an instantiation of a template // class U (like std::pair). template class, typename...> struct is_instantiation: std::false_type {}; template class U, typename... T> struct is_instantiation>: std::true_type {}; // -------------------------------------------------- conjunction // Logical AND template struct conjunction: std::true_type {}; template struct conjunction: std::conditional< T::value, conjunction, std::false_type >::type {}; // -------------------------------------------------- disjunction // Logical OR template struct disjunction: std::false_type {}; template struct disjunction: std::conditional< T::value, std::true_type, disjunction >::type {}; // -------------------------------------------------- negation // Logical NOT template using negation = typename std::conditional< T::value, std::false_type, std::true_type >::type; // -------------------------------------------------- remove_ref_t template using remove_ref_t = typename std::remove_reference::type; // -------------------------------------------------- remove_cvref_t template using remove_cvref_t = typename std::remove_cv::type>::type; // -------------------------------------------------- int_sequence // A call to `make_int_sequence` will return a type `int_sequence<0, 1, 2, ..., N-1>` template struct int_sequence {}; template struct int_sequence_generator: int_sequence_generator {}; template struct int_sequence_generator<0, S...> { typedef int_sequence type; }; template using make_int_sequence = typename int_sequence_generator::type; // -------------------------------------------------- is_bounded_array // Checks if T is an array with a known size. // // is_bounded_array will return true // is_bounded_array will return false // is_bounded_array will return false template struct is_bounded_array_impl: std::false_type {}; template struct is_bounded_array_impl: std::true_type {}; template using is_bounded_array = typename is_bounded_array_impl::type; // -------------------------------------------------- is_invocable // Checks if T is nullary invocable, i.e.: the statement T() is valid. template auto is_invocable_impl(int) -> decltype(std::declval()(), std::true_type{}); template auto is_invocable_impl(...) -> std::false_type; template using is_invocable = decltype(is_invocable_impl(0)); // -------------------------------------------------- is_string_convertible // Checks if T is "string convertible", i.e.: is accepted as argument to a std::string // constructor. template auto is_string_convertible_impl(int) -> decltype(std::string(std::declval()), std::true_type{}); template auto is_string_convertible_impl(...) -> std::false_type; template using is_string_convertible = decltype(is_string_convertible_impl(0)); // -------------------------------------------------- returned_type // Returns the result type of nullary function template auto returned_type_impl(int) -> decltype(std::declval()()); template auto returned_type_impl(...) -> void; template using returned_type = decltype(returned_type_impl(0)); // -------------------------------------------------- resolve_view_t // Applies the pipe operator between a range and a value of type T, and returns the // resulting view type. template auto resolve_view_t_impl(int) -> decltype(std::declval&>() | std::declval()); template auto resolve_view_t_impl(...) -> void; template using resolve_view_t = decltype(resolve_view_t_impl(0)); // -------------------------------------------------- is_sized // Checks if a class `T` has either a `size()` method or a `size(T const&)` overload. template auto has_size_method_impl(int) -> decltype( std::declval().size(), std::true_type{} ); template auto has_size_method_impl(...) -> std::false_type; template auto has_size_overload_impl(int) -> decltype( size(std::declval()), std::true_type{} ); template auto has_size_overload_impl(...) -> std::false_type; template using is_sized = typename disjunction< decltype(has_size_method_impl(0)), decltype(has_size_overload_impl(0)) >::type; template using has_size_method = decltype(has_size_method_impl(0)); template using has_size_function_overload = decltype(has_size_overload_impl(0)); // -------------------------------------------------- is_sentinel_for // Checks if `S` is a sentinel type to an iterator `I` template auto is_sentinel_for_impl(int) -> decltype ( S(std::declval()), std::declval() = std::declval(), std::declval() != std::declval(), std::declval() == std::declval(), std::true_type{} ); template auto is_sentinel_for_impl(...) -> std::false_type; template using is_sentinel_for = decltype(is_sentinel_for_impl(0)); // -------------------------------------------------- is_range // Checks if the type `R` is a range. template auto is_range_impl(int) -> decltype ( begin(std::declval()), end(std::declval()), std::true_type{} ); template auto is_range_impl(...) -> std::false_type; template using is_range = decltype(is_range_impl(0)); // -------------------------------------------------- get_iterator_t // Gets the iterator type of a range `R` template using get_iterator_t = decltype(begin(std::declval())); // -------------------------------------------------- get_reference_t // Gets the reference type (the derreference result) of an iterator `I` template using get_reference_t = decltype(*std::declval()); // -------------------------------------------------- is_input_iterator template auto is_input_iterator_impl(int) -> decltype ( *std::declval(), // is dereferenciable ++std::declval(), // is pre-incrementable std::true_type{} ); template auto is_input_iterator_impl(...) -> std::false_type; template using is_input_iterator = decltype(is_input_iterator_impl(0)); // -------------------------------------------------- is_input_range template using is_input_range = typename conjunction< is_range, is_input_iterator> >::type; // -------------------------------------------------- is_forward_iterator template auto is_forward_iterator_impl(int) -> decltype ( I(std::declval()), // is copyable I(), // is default initializable std::declval() == std::declval(), // is copyable std::declval() != std::declval(), // is copyable std::true_type{} ); template auto is_forward_iterator_impl(...) -> std::false_type; template using is_forward_iterator = typename conjunction< is_input_iterator, is_sentinel_for, decltype(is_forward_iterator_impl(0)) >::type; // -------------------------------------------------- is_forward_range template using is_forward_range = typename conjunction< is_input_range, is_forward_iterator> >::type; // -------------------------------------------------- is_bidirectional_iterator template auto is_bidirectional_iterator_impl(int) -> decltype ( --std::declval(), std::declval()--, std::true_type{} ); template auto is_bidirectional_iterator_impl(...) -> std::false_type; template using is_bidirectional_iterator = typename conjunction< is_forward_iterator, decltype(is_bidirectional_iterator_impl(0)) >::type; // -------------------------------------------------- is_bidirectional_range template using is_bidirectional_range = typename conjunction< is_forward_range, is_bidirectional_iterator> >::type; // -------------------------------------------------- has_push_back_T // Checks if the `C` class has a push_back(`T`) method template auto has_push_back_T_impl(int) -> decltype( std::declval().push_back(std::declval()), std::true_type{} ); template auto has_push_back_T_impl(...) -> std::false_type; template using has_push_back_T = decltype(has_push_back_T_impl(0)); // --------------------------------------------------is_streamable // Checks if `T` has an insertion overload, i.e.: `std::ostream& << T&` template auto is_streamable_impl(int) -> decltype( std::declval() << std::declval(), std::true_type{} ); template auto is_streamable_impl(...) -> std::false_type; template using is_streamable = decltype(is_streamable_impl(0)); // --------------------------------------------------is_stl_formattable // Checks if type `T` is formattable by STL Formatting library #if defined(ICECREAM_STL_FORMAT) template auto is_stl_formattable_impl(int) -> decltype( std::formatter{}, std::true_type{} ); #endif template auto is_stl_formattable_impl(...) -> std::false_type ; template using is_stl_formattable = decltype(is_stl_formattable_impl>(0)); // --------------------------------------------------is_fmt_formattable // Checks if type `T` is formattable by {fmt} library #if defined(ICECREAM_FMT_ENABLED) template auto is_fmt_formattable_impl(int) -> decltype( fmt::formatter{}, std::true_type{} ); #endif template auto is_fmt_formattable_impl(...) -> std::false_type ; template using is_fmt_formattable = decltype(is_fmt_formattable_impl>(0)); // --------------------------------------------------is_baseline_printable template using is_baseline_printable = typename disjunction< is_streamable, is_stl_formattable, is_fmt_formattable >::type; // -------------------------------------------------- has_to_string // Checks if there is a `size(T const&)` overload to a `T` type. template auto has_to_string_impl(int) -> decltype( to_string(std::declval()), std::true_type{} ); template auto has_to_string_impl(...) -> std::false_type; template using has_to_string = decltype(has_to_string_impl(0)); // -------------------------------------------------- is_tuple // Checks if `T` is a tuple like type, i.e.: an instantiation of one of // std::pair or std::tuple. template using is_tuple = typename disjunction< is_instantiation, is_instantiation >::type; // -------------------------------------------------- is_character // Checks if T is character type [const, volatile]?[char, wchar_t, char8_t, char16_t, char32]. template using is_character = typename disjunction< std::is_same::type, char>, std::is_same::type, wchar_t>, #if defined(__cpp_char8_t) std::is_same::type, char8_t>, #endif std::is_same::type, char16_t>, std::is_same::type, char32_t> >::type; // -------------------------------------------------- is_xsig_char template using is_xsig_char = typename disjunction< std::is_same::type, signed char>, std::is_same::type, unsigned char> >::type; // -------------------------------------------------- is_c_string // Checks if T is a C string type, i.e.: either char*, a char[], or a char[N]; of any // character type template using is_c_string = typename disjunction< conjunction< std::is_pointer, is_character::type> >, conjunction< std::is_array, is_character::type> > >::type; // -------------------------------------------------- is_std_string // Checks if T is a std::basic_string template using is_std_string = typename is_instantiation::type>::type; // -------------------------------------------------- is_string_view // Checks if T is a std::basic_string_view template using is_string_view = typename disjunction< #if defined(ICECREAM_STRING_VIEW_HEADER) is_instantiation::type> #endif >::type; // -------------------------------------------------- bypass_baseline_printing // Checks if a type T would be baseline printable, but uses a custom strategy instead template using bypass_baseline_printing = typename disjunction< is_character>, is_c_string>, is_xsig_char>, is_std_string>, is_string_view>, std::is_array> >::type; // -------------------------------------------------- is_variant // Checks if T is a variant type. template using is_variant = typename disjunction< is_instantiation::type> #if defined(ICECREAM_VARIANT_HEADER) , is_instantiation::type> #endif >::type; // -------------------------------------------------- variant_size template struct variant_size; #if defined(ICECREAM_VARIANT_HEADER) template struct variant_size> { static size_t const value = std::variant_size_v>; }; #endif template struct variant_size> { static size_t const value = boost::variant2::variant_size>::value; }; // -------------------------------------------------- is_optional // Checks if T is an optional type. template using is_optional = typename disjunction< #if defined(ICECREAM_OPTIONAL_HEADER) is_instantiation::type> #endif >::type; // -------------------------------------------------- is_not_streamable_ptr // Checks if T is either std::unique_ptr instantiation (Until C++20), or a // boost::scoped_ptr. Both are without an operator<<(ostream&) overload. template using is_unstreamable_ptr = typename disjunction< is_instantiation::type>, is_instantiation::type> >::type; // -------------------------------------------------- is_weak_ptr // Checks if T is a instantiation if either std::weak_ptr or // boost::weak_ptr. template using is_weak_ptr = typename disjunction< is_instantiation::type>, is_instantiation::type> >::type; // -------------------------------------------------- is_valid_prefix // Checks if T can be used as prefix, i.e.: T is a string or a nullary function // returning a type that has a "ostream <<" overload. template using is_valid_prefix = typename disjunction< is_std_string>, is_string_view>, is_c_string>, conjunction< is_invocable, is_streamable> > >::type; // -------------------------------------------------- is_T_output_iterator // Checks if `Iterator` is an output iterator with type `Item` template auto is_T_output_iterator_impl(int) -> decltype( *std::declval() = std::declval(), std::true_type{} ); template auto is_T_output_iterator_impl(...) -> std::false_type; template using is_T_output_iterator = decltype(is_T_output_iterator_impl(0)); // -------------------------------------------------- is_handled_by_clang_dump_struct template using is_handled_by_clang_dump_struct = typename negation< disjunction< is_range, is_tuple>, is_unstreamable_ptr>, is_weak_ptr>, is_std_string>, is_string_view>, is_variant>, is_optional>, is_baseline_printable, is_character>, is_c_string>, std::is_base_of>, std::is_base_of> > >::type; // -------------------------------------------------- custody // Maps a lvalue to a lvalue (T& -> T&) and a rvalue to a value (T&& -> T). // // This will be used when deciding what are the types within a tuple holding the // argument values of a IC_A and IC_FA call. We need to store a rvalue as a value to // keep a prvalue alive, otherwise when calling `IC_A(function, 1, myClass{})` we will // hold dangling references to both arguments. template using custody_t = typename std::conditional< std::is_rvalue_reference::value, typename std::remove_reference::type, T >::type; // -------------------------------------------------- Identity struct Identity { template auto operator()(T&& t) const -> T&& { return std::forward(t); } }; // -------------------------------------------------- min template auto min(T const& lho, T const& rho) -> T const& { return (rho < lho) ? rho : lho; } // -------------------------------------------------- StringView template class BasicStringView { public: static constexpr size_t npos = size_t(-1); using traits_type = std::char_traits; using value_type = CharT; using iterator = CharT const*; using reverse_iterator = std::reverse_iterator; BasicStringView() = default; BasicStringView(value_type const* s, size_t count) : s_{s} , count_{count} {} BasicStringView(value_type const* s) : s_{s} , count_{traits_type::length(s)} {} BasicStringView(std::basic_string const& s) : s_{s.data()} , count_{s.size()} {} #if defined(ICECREAM_STRING_VIEW_HEADER) BasicStringView(std::basic_string_view s) : s_{s.data()} , count_{s.size()} {} #endif BasicStringView(iterator first, iterator last) : s_{first} , count_{static_cast(last - first)} {} auto empty() const -> bool { return this->count_ == 0; } auto size() const -> size_t { return this->count_; } auto data() const -> value_type const* { return this->s_; } auto begin() const -> iterator { return this->s_; } auto end() const -> iterator { return this->s_ + this->count_; } auto rbegin() const -> reverse_iterator { return reverse_iterator(this->s_ + this->count_); } auto rend() const -> reverse_iterator { return reverse_iterator(this->s_); } auto operator[](size_t idx) const -> value_type { return this->s_[idx]; } auto front() const -> value_type { return this->s_[0]; } auto back() const -> value_type { return this->s_[this->count_ - 1]; } friend auto operator==(BasicStringView lho, BasicStringView rho) -> bool { if (lho.count_ != rho.count_) return false; return traits_type::compare(lho.s_, rho.s_, lho.count_) == 0; } auto remove_prefix(size_t n) -> void { this->s_ += n; this->count_ -= n; } auto remove_suffix(size_t n) -> void { this->count_ -= n; } auto substr(size_t pos = 0, size_t count = npos) const -> BasicStringView { return BasicStringView(this->s_ + pos, min(count, this->count_ - pos)); } auto find(value_type const* s, size_t pos = 0) const -> size_t { if (*s == '\0') { return 0; // Empty string is always found at index 0 } for (auto i = pos; i < this->count_; ++i) { if (this->s_[i] == s[0]) { auto j = size_t{1}; while (i + j < this->count_ && this->s_[i + j] == s[j] && s[j] != '\0') { ++j; } // If we reached the end of input string s it was found if (s[j] == '\0') { return i; // Return the starting index } } } return npos; // if not found } auto rfind(value_type ch, size_t pos = npos) const -> size_t { auto const size_ = this->count_; if (size_ == 0) return npos; auto idx = pos < size_ ? pos : size_; for (++idx; idx-- > 0;) { if (this->s_[idx] == ch) return idx; } return npos; } auto trim() -> void { if (this->count_ == 0) return; auto* c0 = this->s_; auto* c1 = this->s_ + this->count_; while ( c0 < c1 && (*c0 == ' ' || *c0 == '\t' || *c0 == '\n' || *c0 == '\r' || *c0 == '\f' || *c0 == '\v') ){ ++c0; } while ( c1 > c0 && ( c1[-1] == ' ' || c1[-1] == '\t' || c1[-1] == '\n' || c1[-1] == '\r' || c1[-1] == '\f' || c1[-1] == '\v' ) ){ --c1; } this->s_ = c0; this->count_ = static_cast(c1 - c0); }; auto to_string() const -> std::basic_string { return std::basic_string(this->s_, this->count_); } private: value_type const* s_ = nullptr; size_t count_ = 0; }; using StringView = BasicStringView; using WStringView = BasicStringView; #if defined(__cpp_char8_t) using U8StringView = BasicStringView; #endif using U16StringView = BasicStringView; using U32StringView = BasicStringView; // -------------------------------------------------- Variant template struct GetHelper; template class Variant { template friend struct GetHelper; private: std::size_t type_index; union { T0 as_t0; T1 as_t1; }; public: Variant() : type_index(0) , as_t0() {} Variant(T0 const& v) : type_index(0) , as_t0(v) {} Variant(T1 const& v) : type_index(1) , as_t1(v) {} Variant(T0&& v) : type_index(0) , as_t0(std::move(v)) {} Variant(T1&& v) : type_index(1) , as_t1(std::move(v)) {} Variant(Variant const& v) : type_index(v.type_index) { switch (this->type_index) { case 0: new (&this->as_t0) T0(v.as_t0); break; case 1: new (&this->as_t1) T1(v.as_t1); break; default: ICECREAM_UNREACHABLE; } } Variant(Variant&& v) : type_index(v.type_index) { switch (this->type_index) { case 0: new (&this->as_t0) T0(std::move(v.as_t0)); break; case 1: new (&this->as_t1) T1(std::move(v.as_t1)); break; default: ICECREAM_UNREACHABLE; } } ~Variant() { switch (this->type_index) { case 0: this->as_t0.~T0(); break; case 1: this->as_t1.~T1(); break; default: ICECREAM_UNREACHABLE; } } auto operator=(Variant const&) -> Variant& = delete; auto operator=(Variant&& other) -> Variant& { if (this != &other) { this->~Variant(); new (this) Variant(std::move(other)); } return *this; } auto index() const -> std::size_t { return this->type_index; } }; template struct GetHelper { auto operator()(Variant const& v) -> T0 const& { if (v.type_index != 0) { throw std::runtime_error("Invalid variant get type"); } return v.as_t0; } auto operator()(Variant& v) -> T0& { if (v.type_index != 0) { throw std::runtime_error("Invalid variant get type"); } return v.as_t0; } }; template struct GetHelper { auto operator()(Variant const& v) -> T1 const& { if (v.type_index != 1) { throw std::runtime_error("Invalid variant get type"); } return v.as_t1; } auto operator()(Variant& v) -> T1& { if (v.type_index != 1) { throw std::runtime_error("Invalid variant get type"); } return v.as_t1; } }; template auto get(Variant& v) -> T& { return GetHelper()(v); } template auto get(Variant const& v) -> T const& { return GetHelper()(v); } // -------------------------------------------------- Optional template class Optional { public: Optional() = default; Optional(T const& v) : storage(v) {} Optional(T&& v) : storage(std::move(v)) {} Optional(Optional const& v) = default; Optional(Optional&& v) = default; auto operator=(Optional const&) -> Optional& = delete; auto operator=(Optional&& other) -> Optional& { if (this != &other) { this->storage = std::move(other.storage); } return *this; } operator bool() const { return this->storage.index() != 0; } auto value() const -> T const& { return get(this->storage); } auto operator*() -> T& { return get(this->storage); } auto operator*() const -> T const& { return get(this->storage); } auto operator->() -> T* { return &get(this->storage); } private: struct empty {}; Variant storage; }; template class Optional { public: Optional() = default; Optional(T& v) : storage(v) {} Optional(T&& v) : storage(std::move(v)) {} Optional(Optional const& v) = default; Optional(Optional&& v) = default; auto operator=(Optional const&) -> Optional& = delete; auto operator=(Optional&& other) -> Optional& { if (this != &other) { this->storage = std::move(other.storage); } return *this; } operator bool() const { return this->storage.index() != 0; } auto value() const -> T const& { return get>(this->storage); } auto operator*() -> T& { return get>(this->storage); } auto operator*() const -> T const& { return get>(this->storage); } auto operator->() -> T* { return &(get>(this->storage).get()); } private: struct empty {}; Variant> storage; }; // -------------------------------------------- Forward declare all make_printing_branch overloads // This is required because the declarations must be visible by some implementation // that can need to delegate the printing to other overload. For example when a // printing a std::vector>>, fist the range implementation will // be called, then it will delegate the printing to the tuple overload, then finally // it will delegate the printing to the operator<<(std::ostream&, T) overload. class Config_; class PrintingNode; template auto do_print_integral( T const&, Config_ const&, std::ostringstream& ) -> typename std::enable_if>::value, PrintingNode>::type; template auto do_print_integral( T const&, Config_ const&, std::ostringstream& ) -> typename std::enable_if>::value, PrintingNode>::type; // Print any class that overloads operator<<(std::ostream&, T) template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_streamable::value && !is_stl_formattable::value && !is_fmt_formattable::value && !bypass_baseline_printing::value , PrintingNode >::type; #if defined(ICECREAM_STL_FORMAT) // Print any class that specializes std::formatter template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_stl_formattable::value && !is_fmt_formattable::value && !bypass_baseline_printing::value , PrintingNode >::type; #endif #if defined(ICECREAM_FMT_ENABLED) // Print any class that specializes fmt::formatter template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_fmt_formattable::value && !bypass_baseline_printing::value , PrintingNode >::type; #endif // Print C string template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if>::value, PrintingNode>::type; // Print std::string and std::string_view template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_std_string>::value || is_string_view>::value, PrintingNode >::type; template auto do_print_char(T, Config_ const&, std::ostringstream&) -> PrintingNode; // Print character template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_character>::value, PrintingNode >::type; // Print signed and unsigned char template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if>::value, PrintingNode>::type; // Print smart pointers without an operator<<(ostream&) overload. template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_unstreamable_ptr>::value && !is_baseline_printable::value, PrintingNode >::type; // Print weak pointer classes template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if>::value, PrintingNode>::type; #if defined(ICECREAM_OPTIONAL_HEADER) // Print std::optional<> classes template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_optional>::value && !is_baseline_printable::value, PrintingNode >::type; #endif template auto do_print_variant( T&&, StringView, Config_ const& ) -> typename std::enable_if>::value, PrintingNode>::type; template auto do_print_variant( T&&, StringView, Config_ const& ) -> typename std::enable_if>::value, PrintingNode>::type; // Print *::variant classes template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_variant>::value && !is_baseline_printable::value, PrintingNode >::type; template auto do_print_tuple( T&&, StringView, Config_ const& ) -> typename std::enable_if< !is_tuple>::value , PrintingNode >::type; template auto do_print_tuple( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_tuple>::value , PrintingNode >::type; // Print tuple like classes template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< is_tuple>::value && !is_baseline_printable::value, PrintingNode >::type; template auto do_print_range( T&&, StringView, Config_ const& ) -> typename std::enable_if::value, PrintingNode>::type; template auto do_print_range( T&&, StringView, Config_ const& ) -> typename std::enable_if::value , PrintingNode>::type; // Print all elements of a range template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< ( is_range::value // it is range && !is_baseline_printable::value // but it is not printable by baseline strategies && !bypass_baseline_printing::value // and it hasn't its own strategy ) || ( // Except by arrays of anything other than character, which are baseline // printable, but should be printed by this strategy instead. std::is_array>::value && !is_c_string>::value ) , PrintingNode >::type; // Print classes deriving from only std::exception and not from boost::exception template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< std::is_base_of>::value && !std::is_base_of>::value && !is_baseline_printable::value, PrintingNode >::type; // Print classes deriving from both std::exception and boost::exception template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if< std::is_base_of>::value && std::is_base_of>::value && !is_baseline_printable::value, PrintingNode >::type; #if defined(ICECREAM_DUMP_STRUCT_CLANG) // Forward declare so that it can be called by the overload printing a collection for instance, // when the elements type shoud be printed using clang dump_struct. template auto make_printing_branch( T&&, StringView, Config_ const& ) -> typename std::enable_if::value, PrintingNode>::type; #endif // -------------------------------------------------- Character transcoders // Read the next UTF-16 char16_t code units and convert them to a UTF-32 char32_t. // // Returns an updated StringView without the processed code units, and a char32_t if // successfully converted or a char16_t with the invalid code unit otherwise. inline auto to_codepoint(U16StringView input) -> std::tuple> { if ((input[0] - 0xD800u) >= 2048u) // is not surrogate { auto const codepoint = char32_t{input[0]}; return std::make_tuple(input.substr(1), codepoint); } else if ( (input[0] & 0xFFFFFC00u) == 0xD800u // is high surrogate && input.size() >= 2 && (input[1] & 0xFFFFFC00u) == 0xDC00u // is low surrogate ){ auto const high = uint32_t{input[0]}; auto const low = uint32_t{input[1]}; auto const codepoint = char32_t{(high << 10) + low - 0x35FDC00u}; return std::make_tuple(input.substr(2), codepoint); } else { // Encoding error return std::make_tuple(input.substr(1), input[0]); } } #if defined(__cpp_char8_t) // Reads the next UTF-8 char8_t code units and convert them to a UTF-32 char32_t. // `input` MUST not be empty. // // Returns an updated StringView without the processed code units, and a char32_t if // successfully converted or a char8_t with the invalid code unit otherwise. inline auto to_codepoint(U8StringView input) -> std::tuple> { if ((input[0] & 0x80) == 0) // 0xxxxxxx { return std::make_tuple(input.substr(1), static_cast(input[0])); } else if ((input[0] & 0xE0) == 0xC0) // 110xxxxx 10xxxxxx { if (input.size() < 2 || (input[1] & 0xC0) != 0x80) { return std::make_tuple(input.substr(1), input[0]); } auto codepoint = static_cast( ((input[0] & 0x1F) << 6) | (input[1] & 0x3F) ); return std::make_tuple(input.substr(2), codepoint); } else if ((input[0] & 0xF0) == 0xE0) // 1110xxxx 10xxxxxx 10xxxxxx { if (input.size() < 3 || (input[1] & 0xC0) != 0x80 || (input[2] & 0xC0) != 0x80) { return std::make_tuple(input.substr(1), input[0]); } auto codepoint = static_cast( ((input[0] & 0x0F) << 12) | ((input[1] & 0x3F) << 6) | (input[2] & 0x3F) ); return std::make_tuple(input.substr(3), codepoint); } else if ((input[0] & 0xF8) == 0xF0) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx { if ( input.size() < 4 || (input[1] & 0xC0) != 0x80 || (input[2] & 0xC0) != 0x80 || (input[3] & 0xC0) != 0x80 ) { return std::make_tuple(input.substr(1), input[0]); } auto codepoint = static_cast( ((input[0] & 0x07) << 18) | ((input[1] & 0x3F) << 12) | ((input[2] & 0x3F) << 6) | (input[3] & 0x3F) ); return std::make_tuple(input.substr(4), codepoint); } else { return std::make_tuple(input.substr(1), input[0]); } } #endif // defined(__cpp_char8_t) // Transcodes an UTF-X string with a CharT code units to an UTF-32 string with // char32_t code units. template auto to_utf32_u32string(BasicStringView input) -> std::u32string { auto result = std::u32string{}; while (!input.empty()) { auto codepoint = Variant{}; std::tie(input, codepoint) = to_codepoint(input); if (codepoint.index() == 0) { result.push_back(get(codepoint)); } else { // Encoding error, print the REPLACEMENT CHARACTER result.push_back(0xFFFD); } } return result; } // Transcodes an UTF-32 string with char32_t code units to an UTF-8 string with char // code units. inline auto to_utf8_string(U32StringView input) -> std::string { auto result = std::string{}; for (auto i = size_t{0}; i < input.size(); ++i) { if (input[i] < 0x80) { result.push_back(static_cast(input[i])); // 0xxxxxxx } else if (input[i] < 0x800) // 00000yyy yyxxxxxx { result.push_back(static_cast(0xC0 | (input[i] >> 6))); // 110yyyyy result.push_back(static_cast(0x80 | (input[i] & 0x3F))); // 10xxxxxx } else if (input[i] < 0x10000) // zzzzyyyy yyxxxxxx { result.push_back(static_cast(0xE0 | (input[i] >> 12))); // 1110zzzz result.push_back(static_cast(0x80 | ((input[i] >> 6) & 0x3F))); // 10yyyyyy result.push_back(static_cast(0x80 | (input[i] & 0x3F))); // 10xxxxxx } else if (input[i] < 0x200000) // 000uuuuu zzzzyyyy yyxxxxxx { result.push_back(static_cast(0xF0 | (input[i] >> 18))); // 11110uuu result.push_back(static_cast(0x80 | ((input[i] >> 12) & 0x3F))); // 10uuzzzz result.push_back(static_cast(0x80 | ((input[i] >> 6) & 0x3F))); // 10yyyyyy result.push_back(static_cast(0x80 | (input[i] & 0x3F))); // 10xxxxxx } else // Encoding error, print the REPLACEMENT CHARACTER { result.push_back(static_cast(0xEF)); result.push_back(static_cast(0xBF)); result.push_back(static_cast(0xBD)); } } return result; } // Converts a wide string to a "execution encoded" narrow multibyte char string, using // the appropriate std::*rtomb function. This function will be used when the running // program has set the current locale. template auto xrtomb(BasicStringView str) -> std::string { auto result = std::string{}; auto state = std::mbstate_t{}; for (auto c : str) { auto mb = std::string(static_cast(MB_CUR_MAX), '\0'); if (tomb(&mb[0], c, &state) == static_cast(-1)) { result.append(""); } else { result.append(mb); } } return result; } inline auto count_utf8_code_point(StringView str) -> size_t { auto result = size_t{0}; auto const n_bytes = str.size(); for (size_t idx = 0; idx < n_bytes; ++idx, ++result) { auto const c = static_cast(str[idx]); if (c<=127) idx+=0; else if ((c & 0xE0) == 0xC0) idx+=1; else if ((c & 0xF0) == 0xE0) idx+=2; else if ((c & 0xF8) == 0xF0) idx+=3; else continue; //invalid utf8, silently move on } return result; } // Checks if the code_unit is a valid codepoint. // Returns 1 if it is a valid codepoint, returns 0 otherwise inline auto is_code_point_valid(uint_least32_t const code_point) -> bool { // U+10FFFF is the current largest defined code point // the [U+D800 - U+DFFF] interval are the surrogates return code_point <= 0x10FFFF && !(0xD800 <= code_point && code_point <= 0xDFFF); } // -------------------------------------------------- Split // Split the `whole` string in pieces, cutting at the indexes within `cut_idxs`. The // chars at the cutting indexes won't be at the result pieces. inline auto split( StringView whole, std::vector const& cut_idxs ) -> std::vector { auto result = std::vector{}; auto cut_a = size_t{0}; for (auto cut_b : cut_idxs) { result.push_back(whole.substr(cut_a, cut_b - cut_a)); cut_a = cut_b + 1; } result.push_back(whole.substr(cut_a, StringView::npos)); return result; } // Split the `whole` string in pieces, cutting at the places where `sep` is. inline auto split(StringView whole, char sep) -> std::vector { auto result = std::vector{}; auto cut_a = size_t{0}; for (auto i = size_t{0}; i < whole.size(); ++i) { if (whole[i] == sep) { result.push_back(whole.substr(cut_a, i - cut_a)); cut_a = i + 1; } } result.push_back(whole.substr(cut_a, StringView::npos)); return result; } // -------------------------------------------------- Prefix class Prefix { private: template struct Function { static_assert(is_invocable::value, ""); Function(T const& func) : func_{func} {} auto operator()() -> std::string { auto buf = std::ostringstream{}; buf << this->func_(); return buf.str(); } T func_; }; std::vector> functions; public: Prefix() = delete; Prefix(Prefix const&) = delete; Prefix(Prefix&&) = default; Prefix& operator=(Prefix const&) = delete; Prefix& operator=(Prefix&&) = default; template Prefix(Ts&&... funcs) : functions{} { // Call this->functions.emplace_back to all funcs (void) std::initializer_list{ ( (void) this->functions.push_back( Function::type>{ std::forward(funcs) } ), 0 )... }; } auto operator()() const -> std::string { auto result = std::string{}; for (auto const& func : this->functions) result.append(func()); return result; } }; // -------------------------------------------------- to_invocable // If value is a string returns an function that returns it. template auto to_invocable(T&& value) -> typename std::enable_if< is_std_string>::value || is_c_string>::value , std::function >::type { auto const str = std::string{value}; return [str]{return str;}; } // If value is already invocable do nothing. template auto to_invocable(T&& value) -> typename std::enable_if< is_invocable::value , T&& >::type { return std::forward(value); } // -------------------------------------------------- Output template class Output { public: Output(T it) : it_{it} {} // Expects `str` in "output encoding". auto operator()(StringView str) -> void { for (auto const& c : str) { *this->it_ = c; ++this->it_; } } private: T it_; }; // -------------------------------------------------- Hereditary // A hereditary object can optionally hold a value, but will always produce a value // when requested, by delegating the request to its parent if needed. template class Hereditary { public: // A child constructed without a value will delegate the value requests to its // parent. Hereditary(Hereditary const& parent) : parent_{&parent} {} // A root object (without a parent) must always have a value. Hereditary(T const& t) : storage_(t) , parent_{nullptr} {} // A root object (without a parent) must always have a value. Hereditary(T&& t) : storage_(std::move(t)) , parent_{nullptr} {} auto operator=(Hereditary const&) -> Hereditary& = delete; auto operator=(T const& t) -> Hereditary& { this->storage_ = t; return *this; } auto operator=(T&& t) -> Hereditary& { this->storage_ = std::move(t); return *this; } auto value() const -> T const& { if (this->storage_) { return this->storage_.value(); } else if (this->parent_) { return this->parent_->value(); } else { ICECREAM_UNREACHABLE; return this->storage_.value(); } } private: Optional storage_; Hereditary const* parent_; }; } // namespace detail // -------------------------------------------------- Config class Config { public: explicit Config(Config const* parent) : enabled_(parent->enabled_) , output_(parent->output_) , prefix_(parent->prefix_) , decay_char_array_(parent->decay_char_array_) , show_c_string_(parent->show_c_string_) , force_range_strategy_(parent->force_range_strategy_) , force_tuple_strategy_(parent->force_tuple_strategy_) , force_variant_strategy_(parent->force_variant_strategy_) , wide_string_transcoder_(parent->wide_string_transcoder_) , unicode_transcoder_(parent->unicode_transcoder_) , output_transcoder_(parent->output_transcoder_) , line_wrap_width_(parent->line_wrap_width_) , include_context_(parent->include_context_) , context_delimiter_(parent->context_delimiter_) {} Config(Config const&) = delete; Config(Config&&) = delete; auto operator=(Config const&) -> Config& = delete; auto operator=(Config&&) -> Config& = delete; auto is_enabled() const -> bool { std::lock_guard guard(this->attribute_mutex); return this->enabled_.value(); } auto enable() -> Config& { std::lock_guard guard(this->attribute_mutex); this->enabled_ = true; return *this; } auto disable() -> Config& { std::lock_guard guard(this->attribute_mutex); this->enabled_ = false; return *this; } // Gatekeeper function to emmit better error messages in invalid input. template auto output(T&& t) -> typename std::enable_if< detail::disjunction< std::is_base_of::type>, detail::has_push_back_T, detail::is_T_output_iterator >::value, Config& >::type { this->set_output(std::forward(t)); return *this; } template auto prefix(Ts&&... value) -> typename std::enable_if< sizeof...(Ts) >= 1 && detail::conjunction< detail::is_valid_prefix>... >::value , Config& >::type { std::lock_guard guard(this->attribute_mutex); this->prefix_ = detail::Prefix(detail::to_invocable(std::forward(value))...); return *this; } auto decay_char_array() const -> bool { std::lock_guard guard(this->attribute_mutex); return this->decay_char_array_.value(); } auto decay_char_array(bool value) -> Config& { std::lock_guard guard(this->attribute_mutex); this->decay_char_array_ = value; return *this; } auto show_c_string() const -> bool { std::lock_guard guard(this->attribute_mutex); return this->show_c_string_.value(); } auto show_c_string(bool value) -> Config& { std::lock_guard guard(this->attribute_mutex); this->show_c_string_ = value; return *this; } auto force_range_strategy() const -> bool { std::lock_guard guard(this->attribute_mutex); return this->force_range_strategy_.value(); } auto force_range_strategy(bool value) -> Config& { std::lock_guard guard(this->attribute_mutex); this->force_range_strategy_ = value; return *this; } auto force_tuple_strategy() const -> bool { std::lock_guard guard(this->attribute_mutex); return this->force_tuple_strategy_.value(); } auto force_tuple_strategy(bool value) -> Config& { std::lock_guard guard(this->attribute_mutex); this->force_tuple_strategy_ = value; return *this; } auto force_variant_strategy() const -> bool { std::lock_guard guard(this->attribute_mutex); return this->force_variant_strategy_.value(); } auto force_variant_strategy(bool value) -> Config& { std::lock_guard guard(this->attribute_mutex); this->force_variant_strategy_ = value; return *this; } auto wide_string_transcoder( std::function&& transcoder ) -> Config& { std::lock_guard guard(this->attribute_mutex); this->wide_string_transcoder_ = [transcoder](detail::WStringView str) -> std::string { return transcoder(str.data(), str.size()); }; return *this; } #if defined(ICECREAM_STRING_VIEW_HEADER) auto wide_string_transcoder( std::function&& transcoder ) -> Config& { std::lock_guard guard(this->attribute_mutex); this->wide_string_transcoder_ = [transcoder](detail::WStringView str) -> std::string { return transcoder({str.data(), str.size()}); }; return *this; } #endif auto unicode_transcoder( std::function&& transcoder ) -> Config& { std::lock_guard guard(this->attribute_mutex); this->unicode_transcoder_ = [transcoder](detail::U32StringView str) -> std::string { return transcoder(str.data(), str.size()); }; return *this; } #if defined(ICECREAM_STRING_VIEW_HEADER) auto unicode_transcoder( std::function&& transcoder ) -> Config& { std::lock_guard guard(this->attribute_mutex); this->unicode_transcoder_ = [transcoder](detail::U32StringView str) -> std::string { return transcoder({str.data(), str.size()}); }; return *this; } #endif auto output_transcoder( std::function&& transcoder ) -> Config& { std::lock_guard guard(this->attribute_mutex); this->output_transcoder_ = [transcoder](detail::StringView str) -> std::string { return transcoder(str.data(), str.size()); }; return *this; } #if defined(ICECREAM_STRING_VIEW_HEADER) auto output_transcoder(std::function&& transcoder) -> Config& { std::lock_guard guard(this->attribute_mutex); this->output_transcoder_ = [transcoder](detail::StringView str) -> std::string { return transcoder({str.data(), str.size()}); }; return *this; } #endif auto line_wrap_width() const -> size_t { std::lock_guard guard(this->attribute_mutex); return this->line_wrap_width_.value(); } auto line_wrap_width(size_t value) -> Config& { std::lock_guard guard(this->attribute_mutex); this->line_wrap_width_ = value; return *this; } auto include_context() const -> bool { std::lock_guard guard(this->attribute_mutex); return this->include_context_.value(); } auto include_context(bool value) -> Config& { std::lock_guard guard(this->attribute_mutex); this->include_context_ = value; return *this; } auto context_delimiter() const -> std::string { std::lock_guard guard(this->attribute_mutex); return this->context_delimiter_.value(); } auto context_delimiter(std::string value) -> Config& { std::lock_guard guard(this->attribute_mutex); this->context_delimiter_ = value; return *this; } protected: Config() = default; auto set_output(std::ostream& stream) -> void { std::lock_guard guard(this->attribute_mutex); using OSIt = std::ostreambuf_iterator; this->output_ = detail::Output{OSIt{stream}}; } template auto set_output(T& container) -> typename std::enable_if< detail::has_push_back_T::value >::type { std::lock_guard guard(this->attribute_mutex); using OSIt = std::back_insert_iterator; this->output_ = detail::Output{OSIt{container}}; } template auto set_output(T iterator) -> typename std::enable_if< detail::is_T_output_iterator::value >::type { std::lock_guard guard(this->attribute_mutex); this->output_ = detail::Output{iterator}; } mutable std::mutex attribute_mutex; detail::Hereditary enabled_{true}; detail::Hereditary> output_{ detail::Output>{ std::ostreambuf_iterator{std::cerr} } }; detail::Hereditary prefix_{ detail::Prefix( []() -> std::string { return "ic| "; } ) }; detail::Hereditary decay_char_array_{false}; detail::Hereditary show_c_string_{true}; detail::Hereditary force_range_strategy_{true}; detail::Hereditary force_tuple_strategy_{true}; detail::Hereditary force_variant_strategy_{true}; detail::Hereditary> wide_string_transcoder_{ [](detail::WStringView str) -> std::string { auto const c_locale = std::string{std::setlocale(LC_ALL, nullptr)}; if (c_locale != "C" && c_locale != "POSIX") { #if defined(_MSC_VER) // Silence a warning due to a deprecated attribute in `std::wcrtomb` #pragma warning(suppress: 4996) return detail::xrtomb(str); #else return detail::xrtomb(str); #endif } else { switch (sizeof(wchar_t)) { case 2: { auto const utf32_str = detail::to_utf32_u32string( detail::U16StringView( reinterpret_cast(str.data()), str.size() ) ); return detail::to_utf8_string(utf32_str); } case 4: return detail::to_utf8_string( detail::U32StringView( reinterpret_cast(str.data()), str.size() ) ); default: return ""; } } } }; detail::Hereditary> unicode_transcoder_{ [](detail::U32StringView str) -> std::string { #ifdef ICECREAM_UCHAR_HEADER auto const c_locale = std::string{std::setlocale(LC_ALL, nullptr)}; if (c_locale != "C" && c_locale != "POSIX") { return detail::xrtomb(str); } else #endif { return detail::to_utf8_string(str); } } }; // Function to convert a string in "execution encoding" to "output encoding" detail::Hereditary> output_transcoder_{ [](detail::StringView str) -> std::string { return str.to_string(); } }; detail::Hereditary line_wrap_width_{70}; detail::Hereditary include_context_{false}; detail::Hereditary context_delimiter_{"- "}; }; namespace detail { // Inherits from icecream::Config, and implements access to attributes which shoud not // be exposed to public. class Config_ : public ::icecream::Config { public: using Config::Config; Config_(Config_ const&) = delete; Config_(Config_&&) = delete; auto operator=(Config_ const&) -> Config_& = delete; auto operator=(Config_&&) -> Config_& = delete; constexpr static size_t INDENT_BASE = 4; static auto global() -> Config_& { static Config_ global_{}; return global_; } auto output() const -> std::function { std::lock_guard guard(this->attribute_mutex); auto const& out = this->output_.value(); return [&out](std::string const& str) -> void { return out(str); }; } auto prefix() const -> std::function { std::lock_guard guard(this->attribute_mutex); auto const& pref = this->prefix_.value(); return [&pref]() -> std::string { return pref(); }; } auto wide_string_transcoder() const -> std::function { std::lock_guard guard(this->attribute_mutex); return this->wide_string_transcoder_.value(); } auto unicode_transcoder() const -> std::function { std::lock_guard guard(this->attribute_mutex); return this->unicode_transcoder_.value(); } auto output_transcoder() const -> std::function { std::lock_guard guard(this->attribute_mutex); return this->output_transcoder_.value(); } private: Config_() = default; }; // -------------------------------------------------- Tree enum class OstreamTypeMode : long { none, debug, binary, BINARY, character, string, non_binary_integer }; inline auto xidx_type_mode() -> int { static auto const xindex = std::ios_base::xalloc(); return xindex; } inline auto getOstreamTypeMode(std::ostringstream& ostrm) -> OstreamTypeMode { return static_cast(ostrm.iword(xidx_type_mode())); } inline auto setOstreamTypeMode(std::ostringstream& ostrm, OstreamTypeMode type) -> void { ostrm.iword(xidx_type_mode()) = static_cast(type); } // Builds an ostringstream and sets its state accordingly to `fmt` string inline auto build_ostream(StringView fmt) -> Optional { // format_spec ::= [[fill]align][sign]["#"][width]["." precision][type] // fill ::= // align ::= "<" | ">" | "^" // sign ::= "+" | "-" // width ::= integer // precision ::= integer // type ::= "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "o" | "s" | "x" | "X" | "?" // integer ::= digit+ // digit ::= "0"..."9" auto os = std::ostringstream{}; os << std::boolalpha; setOstreamTypeMode(os, OstreamTypeMode::none); auto it = std::begin(fmt); auto end_it = std::end(fmt); if (it == end_it) return os; // [[fill]align] { auto fill_char = os.fill(); if (*it != '<' && *it != '>' && *it != '^') { auto la_it = it+1; if (la_it != end_it && (*la_it == '<' || *la_it == '>' || *la_it == '^')) { fill_char = *it; ++it; } } if (it != end_it && *it == '<') { os << std::left << std::setfill(fill_char); ++it; } else if (it != end_it && *it == '>') { os << std::right << std::setfill(fill_char); ++it; } else if (it != end_it && *it == '^') { os << std::internal << std::setfill(fill_char); ++it; } } // [sign] if (it != end_it && *it == '+') { os << std::showpos; ++it; } else if (it != end_it && *it == '-') { os << std::noshowpos; ++it; } // ["#"] if (it != end_it && *it == '#') { os << std::showbase << std::showpoint; ++it; } // [width] { auto b_it = it; while (it != end_it && *it >= '0' && *it <= '9') ++it; if (it != b_it) { os << std::setw(std::stoi(std::string(b_it, it))); } } // ["." precision] if (it != end_it && *it == '.') { auto b_it = it+1; auto p_it = b_it; while (p_it != end_it && *p_it >= '0' && *p_it <= '9') ++p_it; if (p_it != b_it) { os << std::setprecision(std::stoi(std::string(b_it, p_it))); it = p_it; } } // [type] if (it != end_it && *it == 'a') { os << std::hexfloat << std::nouppercase; ++it; } else if (it != end_it && *it == 'A') { os << std::hexfloat << std::uppercase; ++it; } else if (it != end_it && *it == 'b') { setOstreamTypeMode(os, OstreamTypeMode::binary); ++it; } else if (it != end_it && *it == 'B') { setOstreamTypeMode(os, OstreamTypeMode::BINARY); ++it; } else if (it != end_it && *it == 'c') { setOstreamTypeMode(os, OstreamTypeMode::character); ++it; } else if (it != end_it && *it == 'd') { os << std::dec << std::noboolalpha; setOstreamTypeMode(os, OstreamTypeMode::non_binary_integer); ++it; } else if (it != end_it && *it == 'e') { os << std::scientific << std::nouppercase; ++it; } else if (it != end_it && *it == 'E') { os << std::scientific << std::uppercase; ++it; } else if (it != end_it && *it == 'f') { os << std::fixed << std::nouppercase; ++it; } else if (it != end_it && *it == 'F') { os << std::fixed << std::uppercase; ++it; } else if (it != end_it && *it == 'g') { os << std::defaultfloat << std::nouppercase; ++it; } else if (it != end_it && *it == 'G') { os << std::defaultfloat << std::uppercase; ++it; } else if (it != end_it && *it == 'o') { os << std::oct << std::noboolalpha; setOstreamTypeMode(os, OstreamTypeMode::non_binary_integer); ++it; } else if (it != end_it && *it == 's') { setOstreamTypeMode(os, OstreamTypeMode::string); ++it; } else if (it != end_it && *it == 'x') { os << std::hex << std::nouppercase << std::noboolalpha; setOstreamTypeMode(os, OstreamTypeMode::non_binary_integer); ++it; } else if (it != end_it && *it == 'X') { os << std::hex << std::uppercase << std::noboolalpha; setOstreamTypeMode(os, OstreamTypeMode::non_binary_integer); ++it; } else if (it != end_it && *it == '?') { setOstreamTypeMode(os, OstreamTypeMode::debug); ++it; } if (it == end_it) { return os; } else { return {}; } } // char -> char inline auto transcoder_dispatcher(Config_ const&, StringView str) -> std::string { return str.to_string(); } // wchar_t -> char inline auto transcoder_dispatcher(Config_ const& config, WStringView str) -> std::string { return config.wide_string_transcoder()(str); } #if defined(__cpp_char8_t) // char8_t -> char inline auto transcoder_dispatcher(Config_ const& config, U8StringView str) -> std::string { return config.unicode_transcoder()(to_utf32_u32string(str)); } #endif // char16_t -> char inline auto transcoder_dispatcher(Config_ const& config, U16StringView str) -> std::string { return config.unicode_transcoder()(to_utf32_u32string(str)); } // char32_t -> char inline auto transcoder_dispatcher(Config_ const& config, U32StringView str) -> std::string { return config.unicode_transcoder()(str); } class PrintingNode { private: struct Stem { std::string open; std::string separator; std::string close; std::vector children; Stem( StringView open_, StringView separator_, StringView close_, std::vector&& children_ ) : open(open_.to_string()) , separator(separator_.to_string()) , close(close_.to_string()) , children(std::move(children_)) {} }; Variant content; size_t n_code_unit; size_t n_code_point; auto is_leaf() const -> bool { return this->content.index() == 0; } auto get_leaf() const -> StringView { return get(this->content); } auto get_stem() -> Stem& { return get(this->content); } auto get_stem() const -> Stem const& { return get(this->content); } public: PrintingNode() = delete; PrintingNode(PrintingNode const&) = delete; PrintingNode& operator=(PrintingNode const&) = delete; PrintingNode(PrintingNode&& other) : content(std::move(other.content)) , n_code_unit(other.n_code_unit) , n_code_point(other.n_code_point) {} PrintingNode& operator=(PrintingNode&& other) { if (this != &other) { this->~PrintingNode(); new (this) PrintingNode(std::move(other)); } return *this; } explicit PrintingNode(StringView leaf) : content(leaf.to_string()) , n_code_unit(leaf.size()) , n_code_point(count_utf8_code_point(leaf)) {} PrintingNode( StringView open, StringView separator, StringView close, std::vector children ) : content( Stem(open, separator, close, std::move(children)) ) , n_code_unit{ [this]() { // The enclosing chars. auto result = this->get_stem().open.size() + this->get_stem().close.size(); // count the size of each child for (auto const& child : this->get_stem().children) { result += child.n_code_unit; } // The separators. if (this->get_stem().children.size() > 1) { result += (this->get_stem().children.size() - 1) * this->get_stem().separator.size(); } return result; }() } , n_code_point{ [this]() { // The enclosing chars. auto result = count_utf8_code_point(this->get_stem().open) + count_utf8_code_point(this->get_stem().close); // count the size of each child for (auto const& child : this->get_stem().children) { result += child.n_code_point; } // The separators. if (this->get_stem().children.size() > 1) { result += (this->get_stem().children.size() - 1) * count_utf8_code_point(this->get_stem().separator); } return result; }() } {} auto code_point_length() const -> size_t { return this->n_code_point; } // Search among the children of `this` Tree, by a Tree having `key` as its // content. Returns `nullptr` if no child has been found. auto find_leaf(StringView key) -> PrintingNode* { auto to_visit = std::vector{this}; while (!to_visit.empty()) { auto current = to_visit.back(); to_visit.pop_back(); if (!current->is_leaf()) { for (auto& child : current->get_stem().children) { to_visit.push_back(&child); } } else if (current->get_leaf() == key) { return current; } } return nullptr; } // Print the branch content, in a unique line auto print() const -> std::string { auto result = std::string{}; result.reserve(this->n_code_unit); if (this->is_leaf()) { result = this->get_leaf().to_string(); } else { result = this->get_stem().open; for ( auto it = this->get_stem().children.cbegin(); it != this->get_stem().children.cend(); ){ result += it->print(); ++it; if (it != this->get_stem().children.cend()) { result += this->get_stem().separator; } } result += this->get_stem().close; } return result; } auto print(size_t const indent_level, size_t const line_wrap_width) const -> std::string { auto result = std::string{}; result.reserve(this->n_code_unit); // Leaf nodes will print its content regardless of being longer than the maximum line // length, since there is no children to be split. if (this->is_leaf()) { result = this->get_leaf().to_string(); } else { result = this->get_stem().open; result += "\n"; auto const indent_lenght = indent_level * Config_::INDENT_BASE ; for ( auto it = this->get_stem().children.cbegin(); it != this->get_stem().children.cend(); ){ result += std::string(indent_level * Config_::INDENT_BASE, ' '); if (indent_lenght + it->n_code_point <= line_wrap_width) { result += it->print(); } else { result += it->print(indent_level+1, line_wrap_width); } ++it; if (it != this->get_stem().children.cend()) { result += this->get_stem().separator; result += "\n"; } else { result += "\n"; } } result += std::string((indent_level-1) * Config_::INDENT_BASE, ' ') + this->get_stem().close; } return result; } }; // -------------------------------------------------- make_printing_branch functions template auto do_print_integral( T const&, Config_ const&, std::ostringstream& ) -> typename std::enable_if>::value, PrintingNode>::type { ICECREAM_UNREACHABLE; return PrintingNode(""); } template auto do_print_integral( T const& value, Config_ const& config, std::ostringstream& ostrm ) -> typename std::enable_if>::value, PrintingNode>::type { auto print_binary = [&](bool uppercase_base) { if (ostrm.flags() & std::ios_base::showbase) { ostrm << (uppercase_base ? "0B" : "0b"); } auto const n_bits = sizeof(value) * CHAR_BIT; // Flag to skip leading zeros auto found_one = false; // Loop through each bit from the most significant to the least significant for (auto i = n_bits; (i--) > 0;) { auto const bit = (value >> i) & 1; if (bit == 1) found_one = true; if (found_one || i == 0) // Always print at least the last bit { ostrm << bit; } } }; switch (getOstreamTypeMode(ostrm)) { case OstreamTypeMode::character: if (std::is_same, bool>::value) { return PrintingNode("*Error* in formatting string"); } else if (value > static_cast(127)) { return PrintingNode("*Error* Integral value outside the range of the char type"); } else { return do_print_char(static_cast(value), config, ostrm); } case OstreamTypeMode::none: case OstreamTypeMode::non_binary_integer: ostrm << value; return PrintingNode(ostrm.str()); case OstreamTypeMode::binary: print_binary(false); return PrintingNode(ostrm.str()); case OstreamTypeMode::BINARY: print_binary(true); return PrintingNode(ostrm.str()); case OstreamTypeMode::debug: case OstreamTypeMode::string: default: return PrintingNode("*Error* in formatting string"); } } // Print any class that overloads operator<<(std::ostream&, T) template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_streamable::value && !is_stl_formattable::value && !is_fmt_formattable::value && !bypass_baseline_printing::value , PrintingNode >::type { if (config.force_variant_strategy() && is_variant>::value) { return do_print_variant(std::forward(value), fmt, config); } auto mb_ostrm = build_ostream(fmt); if (!mb_ostrm) { return PrintingNode("*Error* in formatting string"); } if (std::is_integral>::value) { return do_print_integral(value, config, *mb_ostrm); } else { *mb_ostrm << value; return PrintingNode(mb_ostrm->str()); } } #if defined(ICECREAM_STL_FORMAT_RANGES) // This class will wrap a range T, signaling it as "hijacked" to avoid a circular // inheritance in the `std::formatter` specialization below. template struct HijackTag { T const& value; HijackTag(T const& t) : value(t) {} auto begin() const { return std::begin(value); } auto end() const { return std::end(value); } }; }} namespace std { // Starting from C++23 the formatting library has a concept specialization to print // any *range*. Because of this any *range* that Icecream-cpp could try to print would // be handled by the STL's Formatting function overload of the *baseline printable* // strategy. // // We would like that the generic range case would continue being handled by the // *range types* strategy, but other than that, any `formatter` struct further // specializing a range should take precedence and the *baseline printable* strategy // be the one used. For example, when printing a `std::vector` the *range types* // strategy should be used, but if there exist a `std::formatter>` // specialization, the *baseline printable* strategy should be used instead. // // Here we will hijack any instantiation of the generic range specialization, inherit // from the hijacked STL version so that we have the required methods implemented, and // insert an `Hijacked` typedef to signalize that this specialization was used, so // that Icecream-cpp knows that the type `T` is a range, and is free to delegate the // printing of `T` to the ranges strategy. template< ranges::forward_range T> requires (format_kind> != range_format::disabled) && formattable, char> && (!::icecream::detail::is_instantiation<::icecream::detail::HijackTag, remove_cvref_t>::value) && (!::icecream::detail::is_c_string::value) && (!::icecream::detail::is_std_string>::value) && (!::icecream::detail::is_string_view>::value) && (!std::is_array>::value) struct formatter : public formatter<::icecream::detail::HijackTag>, char> { using Hijacked = int; }; } namespace icecream { namespace detail { #endif // defined(ICECREAM_STL_FORMAT_RANGES) #if defined(ICECREAM_STL_FORMAT) template struct has_hijacked_tag : std::false_type {}; template struct has_hijacked_tag< T, typename std::enable_if< !std::is_void>::Hijacked>::value >::type > : std::true_type {}; // Print any class that specializes std::formatter template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_stl_formattable::value && !is_fmt_formattable::value && !bypass_baseline_printing::value , PrintingNode >::type { if (config.force_range_strategy() && has_hijacked_tag::value) { return do_print_range(std::forward(value), fmt, config); } else if (config.force_tuple_strategy() && is_tuple>::value) { return do_print_tuple(std::forward(value), fmt, config); } else if (config.force_variant_strategy() && is_variant>::value) { return do_print_variant(std::forward(value), fmt, config); } try { return PrintingNode( std::vformat("{:" + fmt.to_string() + "}", std::make_format_args(value)) ); } catch (std::format_error const&) { return PrintingNode("*Error* in formatting string"); } } #endif // defined(ICECREAM_STL_FORMAT) #if defined(ICECREAM_FMT_ENABLED) // Print any class that specializes fmt::formatter template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_fmt_formattable::value && !bypass_baseline_printing::value , PrintingNode >::type { if (config.force_range_strategy() && is_range>::value) { return do_print_range(std::forward(value), fmt, config); } else if (config.force_tuple_strategy() && is_tuple>::value) { return do_print_tuple(std::forward(value), fmt, config); } else if (config.force_variant_strategy() && is_variant>::value) { return do_print_variant(std::forward(value), fmt, config); } return PrintingNode( fmt::vformat("{:" + fmt.to_string() + "}", fmt::make_format_args(value)) ); } #endif // Prints all characters encoded in "execution encoding" template auto print_text( StringView code_units, Config_ const&, std::ostringstream& ostrm ) -> void { switch (getOstreamTypeMode(ostrm)) { case OstreamTypeMode::character: case OstreamTypeMode::string: ostrm << code_units.to_string(); break; case OstreamTypeMode::none: case OstreamTypeMode::debug: for (auto cu : code_units) { switch (cu) { case '\a': ostrm << "\\a"; break; case '\b': ostrm << "\\b"; break; case '\t': ostrm << "\\t"; break; case '\n': ostrm << "\\n"; break; case '\v': ostrm << "\\v"; break; case '\f': ostrm << "\\f"; break; case '\r': ostrm << "\\r"; break; case '\0': ostrm << "\\u{0}"; break; case Quote: ostrm << "\\" << Quote; break; default: ostrm << cu; } } break; case OstreamTypeMode::binary: case OstreamTypeMode::BINARY: case OstreamTypeMode::non_binary_integer: default: ICECREAM_UNREACHABLE; } } // Prints char32_t characters template auto print_text( U32StringView code_units, Config_ const& config, std::ostringstream& ostrm ) -> void { switch (getOstreamTypeMode(ostrm)) { case OstreamTypeMode::character: case OstreamTypeMode::string: ostrm << config.unicode_transcoder()(code_units); break; case OstreamTypeMode::none: case OstreamTypeMode::debug: for (auto cu : code_units) { if (is_code_point_valid(cu)) { print_text( config.unicode_transcoder()(U32StringView(&cu, 1)), config, ostrm ); } else { ostrm << "\\x{" << std::hex << std::nouppercase << std::char_traits::to_int_type(cu) << "}"; } } break; case OstreamTypeMode::binary: case OstreamTypeMode::BINARY: case OstreamTypeMode::non_binary_integer: default: ICECREAM_UNREACHABLE; } } // Prints char8_t and char16_t characters template auto print_text( BasicStringView code_units, Config_ const& config, std::ostringstream& ostrm ) -> void { switch (getOstreamTypeMode(ostrm)) { case OstreamTypeMode::character: case OstreamTypeMode::string: ostrm << config.unicode_transcoder()(to_utf32_u32string(code_units)); break; case OstreamTypeMode::none: case OstreamTypeMode::debug: while (!code_units.empty()) { auto result = Variant{}; std::tie(code_units, result) = to_codepoint(code_units); if (result.index() == 0) { print_text( U32StringView(&get(result), 1), config, ostrm ); } else { ostrm << "\\x{" << std::hex << std::nouppercase << std::char_traits::to_int_type(get(result)) << "}"; } } break; case OstreamTypeMode::binary: case OstreamTypeMode::BINARY: case OstreamTypeMode::non_binary_integer: default: ICECREAM_UNREACHABLE; } } // Prints wchar_t characters template auto print_text( WStringView code_units, Config_ const& config, std::ostringstream& ostrm ) -> void { switch (getOstreamTypeMode(ostrm)) { case OstreamTypeMode::character: case OstreamTypeMode::string: ostrm << config.wide_string_transcoder()(code_units); break; case OstreamTypeMode::none: case OstreamTypeMode::debug: print_text(config.wide_string_transcoder()(code_units), config, ostrm); break; case OstreamTypeMode::binary: case OstreamTypeMode::BINARY: case OstreamTypeMode::non_binary_integer: default: ICECREAM_UNREACHABLE; } } template auto do_print_string( BasicStringView value, Config_ const& config, std::ostringstream& ostrm ) -> PrintingNode { switch (getOstreamTypeMode(ostrm)) { case OstreamTypeMode::string: print_text<'"'>(value, config, ostrm); return PrintingNode(ostrm.str()); case OstreamTypeMode::none: case OstreamTypeMode::debug: ostrm << '"'; print_text<'"'>(value, config, ostrm); ostrm << '"'; return PrintingNode(ostrm.str()); case OstreamTypeMode::binary: case OstreamTypeMode::BINARY: case OstreamTypeMode::character: case OstreamTypeMode::non_binary_integer: default: return PrintingNode("*Error* in formatting string"); } } // Print C string template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if>::value, PrintingNode>::type { using CharT = remove_cvref_t< typename std::remove_pointer< typename std::decay::type >::type >; auto mb_ostrm = build_ostream(fmt); if (!mb_ostrm) { return PrintingNode("*Error* in formatting string"); } // If config.decay_char_array() is not true, any character array with a known size // should be printed as a range if (is_bounded_array>::value && !config.decay_char_array()) { return do_print_range(std::forward(value), fmt, config); } if (config.show_c_string()) { do_print_string(value, config, *mb_ostrm); } else { *mb_ostrm << reinterpret_cast(value); } return PrintingNode(mb_ostrm->str()); } // Print std::string and std::string_view template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_std_string>::value || is_string_view>::value, PrintingNode >::type { using CharT = typename remove_ref_t::value_type; auto mb_ostrm = build_ostream(fmt); if (!mb_ostrm) { return PrintingNode("*Error* in formatting string"); } return do_print_string(value, config, *mb_ostrm); } template auto do_print_char(T value, Config_ const& config, std::ostringstream& ostrm) -> PrintingNode { using CharT = remove_cvref_t; switch (getOstreamTypeMode(ostrm)) { case OstreamTypeMode::character: print_text<'\''>(BasicStringView(&value, 1), config, ostrm); return PrintingNode(ostrm.str()); case OstreamTypeMode::none: case OstreamTypeMode::debug: ostrm << '\''; print_text<'\''>(BasicStringView(&value, 1), config, ostrm); ostrm << '\''; return PrintingNode(ostrm.str()); case OstreamTypeMode::non_binary_integer: case OstreamTypeMode::binary: case OstreamTypeMode::BINARY: return do_print_integral(std::char_traits::to_int_type(value), config, ostrm); case OstreamTypeMode::string: default: return PrintingNode("*Error* in formatting string"); } } // Print character template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_character>::value, PrintingNode >::type { auto mb_ostrm = build_ostream(fmt); if (!mb_ostrm) { return PrintingNode("*Error* in formatting string"); } return do_print_char(value, config, *mb_ostrm); } // Print signed and unsigned char template auto make_printing_branch( T&& value, StringView fmt, Config_ const& ) -> typename std::enable_if>::value, PrintingNode>::type { using T0 = typename std::conditional< std::is_signed::value, int, unsigned int >::type; auto mb_ostrm = build_ostream(fmt); if (!mb_ostrm) { return PrintingNode("*Error* in formatting string"); } *mb_ostrm << static_cast(value); return PrintingNode(mb_ostrm->str()); } // Print smart pointers without an operator<<(ostream&) overload. template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< // On C++20 unique_ptr will have a << overload. is_unstreamable_ptr>::value && !is_baseline_printable::value, PrintingNode >::type { return make_printing_branch( reinterpret_cast(value.get()), fmt, config ); } // Print weak pointer classes template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if>::value, PrintingNode>::type { return value.expired() ? PrintingNode("expired") : make_printing_branch(value.lock(), fmt, config); } #if defined(ICECREAM_OPTIONAL_HEADER) // Print std::optional<> classes template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_optional>::value && !is_baseline_printable::value, PrintingNode >::type { auto container_fmt = StringView{}; auto element_fmt = StringView{}; auto const cut_idx = fmt.find(":"); if (cut_idx != StringView::npos) { auto const fmt_parts = split(fmt, std::vector{cut_idx}); container_fmt = fmt_parts[0]; element_fmt = fmt_parts[1]; } else { container_fmt = fmt; } // std::optional hasn't any container formatting string if (!container_fmt.empty()) { return PrintingNode("*Error* in formatting string"); } return value.has_value() ? make_printing_branch(*value, element_fmt, config) : PrintingNode("nullopt"); } #endif template struct Visitor { Visitor(Variant const& variant, StringView fmt, Config_ const& config) : variant_(variant) , elements_fmt( [&]() -> std::vector { if (fmt.empty()) { // No element specifications is equivalent to all them having an empty string return std::vector(variant_size::value, StringView{}); } auto const sep = fmt[0]; fmt.remove_prefix(1); return split(fmt, sep); }() ) , config_(config) {} template auto operator()(T const& arg) -> PrintingNode { if (this->elements_fmt.size() != variant_size::value) { return PrintingNode("*Error* in formatting string"); } auto const fmt = this->elements_fmt.at(this->variant_.index()); return make_printing_branch(arg, fmt, this->config_); } Variant const& variant_; std::vector elements_fmt; Config_ const& config_; }; template auto do_print_variant( T&&, StringView, Config_ const& ) -> typename std::enable_if>::value, PrintingNode>::type { ICECREAM_UNREACHABLE; return PrintingNode(""); } template auto do_print_variant( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if>::value, PrintingNode>::type { return visit(Visitor>(value, fmt, config), value); } // Print *::variant classes template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_variant>::value && !is_baseline_printable::value, PrintingNode >::type { return do_print_variant(std::forward(value), fmt, config); } template inline auto tuple_traverser( int_sequence, T const& t, std::vector const& fmt, Config_ const& config ) -> std::vector { auto result = std::vector{}; (void) std::initializer_list{ ( result.push_back( make_printing_branch(std::get(t), fmt[N], config) ), 0 )... }; return result; } template auto do_print_tuple( T&&, StringView, Config_ const& ) -> typename std::enable_if< !is_tuple>::value , PrintingNode >::type { ICECREAM_UNREACHABLE; return PrintingNode(""); } template auto do_print_tuple( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_tuple>::value , PrintingNode >::type { auto const tuple_size = std::tuple_size>::value; auto opening = std::string{"("}; auto separator = std::string{", "}; auto closing = std::string{")"}; if (!fmt.empty() && fmt[0] == 'n') { opening = ""; separator = ", "; closing = ""; fmt.remove_prefix(1); } else if (!fmt.empty() && fmt[0] == 'm') { if (tuple_size != 2) { return PrintingNode( "<*Error*: the `m` specifier is only valid for pairs or 2-tuples>" ); } opening = ""; separator = ": "; closing = ""; fmt.remove_prefix(1); } auto const elements_fmt = [&]() -> std::vector { if (fmt.empty()) { // No element specifications is equivalent to all them having an empty string return std::vector(tuple_size, StringView{}); } auto const sep = fmt[0]; fmt.remove_prefix(1); return split(fmt, sep); }(); if (elements_fmt.size() != tuple_size) { return PrintingNode( "<*Error*: expected " + std::to_string(tuple_size) + " element formatting specifiers>" ); } return PrintingNode( opening, separator, closing, tuple_traverser( make_int_sequence(), value, elements_fmt, config ) ); } // Print tuple like classes template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< is_tuple>::value && !is_baseline_printable::value, PrintingNode >::type { return do_print_tuple(std::forward(value), fmt, config); } // -------------------------------------------------- Range classes // Direct representation of a slicing string. struct Slice { Optional start; Optional stop; Optional step; // Receives a slicing string, like "[:-2:2]" and returns an instance of `Slice` // class that represents it. If the string is an invalid slicing, returns nothing. static auto build(StringView fmt) -> Optional { if (fmt.empty()) { return Slice{{}, {}, {}}; } // All iterable slicings must start with '[' and finish with ']' if (fmt.front() != '[' || fmt.back() != ']') { return {}; } // Remove the opening '[' and the closing ']' fmt.remove_prefix(1); fmt.remove_suffix(1); // An empty '[]' slicing is invalid if (fmt.empty()) { return {}; } auto indexes = std::vector>{}; for (auto idx_string : split(fmt, ':')) { idx_string.trim(); if (idx_string.empty()) { indexes.push_back({}); continue; } auto end = static_cast(nullptr); auto const null_terminated = idx_string.to_string(); auto const lnum = strtol(null_terminated.data(), &end, 10); if ( // if idx_string in its whole isn't a number end != (null_terminated.data() + null_terminated.size()) // or if there is an overflow on long int || ( ( lnum == std::numeric_limits::max() || lnum == std::numeric_limits::lowest() ) && errno == ERANGE ) // or if there is an overflow to convert from long to ptrdiff || ( lnum > std::numeric_limits::max() || lnum < std::numeric_limits::lowest() ) ) { return {}; } indexes.push_back(lnum); } // If indexing an element ("[3]") instead of a proper slicing. if (indexes.size() == 1 && indexes[0]) { auto const idx = *indexes[0]; return Slice{idx, idx+1, {}}; } while (indexes.size() < 3) { indexes.push_back({}); } if (indexes.size() != 3) return {}; return Slice{indexes[0], indexes[1], indexes[2]}; } }; // Advances `it` for `n` steps. Will stop before advancing `n` steps if `sentinel` is // reached. Returns the actual displacement. template auto advance_it(I it, S const& sentinel, size_t n) -> I { auto offset = size_t{0}; while (it != sentinel && offset != n) { std::advance(it, 1); offset += 1; } return it; } // A functor whose nullary function call operator will return an Optional element in // the range [it, sentinel) spaced by `step`. After exhausting the range, any call // will return an empty Optional. template class SliceFunctorImpl { private: I it_; S sentinel_; size_t step_; public: SliceFunctorImpl(I it, S sentinel, size_t step) : it_(it) , sentinel_(sentinel) , step_(step) {} auto operator()() -> Optional> { if (this->it_ == this->sentinel_) return {}; auto old_it = this->it_; this->it_ = advance_it(this->it_, this->sentinel_, this->step_); return {*old_it}; } }; template using SliceFunctor = std::function>>()>; template auto make_slice_functor(I iterator, S sentinel, size_t step) -> SliceFunctor { return SliceFunctorImpl{iterator, sentinel, step}; } template auto build_slice_functor_p( R&& range, Optional mb_start, Optional mb_stop, size_t step ) -> SliceFunctor { auto const start_it = mb_start ? advance_it(begin(range), end(range), *mb_start) : begin(range); if (!mb_stop) { return make_slice_functor(start_it, end(range), step); } else if (is_sized::value) // So it has a value and it is normalized { auto stop_it = begin(range); std::advance(stop_it, static_cast(*mb_stop)); return make_slice_functor(start_it, stop_it, step); } else { return make_slice_functor( start_it, advance_it(begin(range), end(range), *mb_stop), step ); } } template auto build_slice_functor_n( R&& range, Optional mb_start, Optional mb_stop, size_t step ) -> typename std::enable_if< is_bidirectional_range::value, SliceFunctor >::type { auto make_reverse_iterator = [](get_iterator_t it) { return std::reverse_iterator>(it); }; auto start_it = mb_start ? advance_it(begin(range), end(range), *mb_start) : // An Iterator instance evaluating equal to the range Sentinel. Since we will // backwardly iterate over the range, that is the default starting point. advance_it(begin(range), end(range), std::numeric_limits::max()); // When derreferencing a `reverse_iterator`, the retrieved element will be one // before the element pointed by IT. So here we fix that offset. if (start_it != end(range)) ++start_it; // If we don't have a stop value, set it to the beginning of the range if (!mb_stop) { auto stop_it = begin(range); return make_slice_functor( make_reverse_iterator(start_it), make_reverse_iterator(stop_it), step ); } // If we do have a stop value, and its value is normalized within [0, range_size] else if (is_sized::value) { auto stop_it = begin(range); std::advance(stop_it, static_cast(*mb_stop)); if (stop_it != end(range)) ++stop_it; return make_slice_functor( make_reverse_iterator(start_it), make_reverse_iterator(stop_it), step ); } // If we do have a stop value, and its value is not normalized else { auto stop_it = advance_it(begin(range), end(range), *mb_stop); return make_slice_functor( make_reverse_iterator(start_it), make_reverse_iterator(stop_it), step ); } } template auto build_slice_functor_n(R&& r, Optional, Optional, size_t) -> typename std::enable_if< !is_bidirectional_range::value, SliceFunctor >::type { ICECREAM_UNREACHABLE; return make_slice_functor(begin(r), begin(r), 0); } template auto maybe_get_size( R&& range ) -> typename std::enable_if< has_size_function_overload::value, Optional >::type { return size(range); } template auto maybe_get_size( R&& range ) -> typename std::enable_if< has_size_method::value && !has_size_function_overload::value, Optional >::type { return range.size(); } template auto maybe_get_size( R&& ) -> typename std::enable_if< !is_sized::value, Optional >::type { return {}; } template auto maybe_make_slice_functor( R&& range, Slice const& slice ) -> Variant> { auto const mb_range_size = maybe_get_size(range); if ( mb_range_size && *mb_range_size > static_cast(std::numeric_limits::max()) ) { return std::string(""); } // If the range size is unknown it is not possible to normalize a negative index. // So here we return an error message if we have that situation. if ( !mb_range_size && ((slice.start && *slice.start < 0) || (slice.stop && *slice.stop < 0)) ) { return std::string{""}; } auto const step = slice.step ? *slice.step : 1; if (step == 0) { return std::string{""}; } if (!is_bidirectional_range::value && step < 0) { return std::string{""}; } auto const mb_start = [&]() -> Optional { if (!slice.start) { return {}; } else if (mb_range_size) { auto const range_size = static_cast(*mb_range_size); auto const idx = (*slice.start >= 0) ? *slice.start : *slice.start + range_size; return (idx < 0) ? 0 : (idx > range_size) ? static_cast(range_size) : static_cast(idx); } else { // If range is unsized, start was ensured as non-negative above return static_cast(*slice.start); } }(); auto const mb_stop = [&]() -> Optional { if (!slice.stop) { return {}; } else if (mb_range_size) { auto const range_size = static_cast(*mb_range_size); auto const idx = (*slice.stop >= 0) ? *slice.stop : *slice.stop + range_size; if (step < 0 && idx < 0) { return {}; } else { return (idx < 0) ? 0 : (idx > range_size) ? static_cast(range_size) : static_cast(idx); } } else { // If range is unsized, stop was ensured as non-negative above return static_cast(*slice.stop); } }(); // If the stop point would be placed before the start point. Return an empty slicing. if ( mb_start && mb_stop && ( (step > 0 && *mb_stop <= *mb_start) || (step < 0 && *mb_stop >= *mb_start) ) ) { return build_slice_functor_p(range, 0, 0, 1); } // At here, we know for sure that for all start, stop, and step; if they have a // value, that value is non-negative and step non zero too. In addition to that, // stop for sure is placed at the same location as start or in a place after // start. if (step > 0) { return build_slice_functor_p(range, mb_start, mb_stop, static_cast(step)); } else // step < 0 { return build_slice_functor_n(range, mb_start, mb_stop, static_cast(-step)); } } // Receives a range formatting string, "[:3]:#x" for instance, and splits it in a // pair: the range formatting itself ("[:3]") and the elements formatting ("#x"). The // cut point is the leftmost colon that is outside of a square bracket pair. inline auto split_range_fmt_string(StringView fmt) -> std::tuple { auto is_inside_square_brackets = false; for (auto i = size_t{0}; i < fmt.size(); ++i) { if (fmt[i] == '[') { is_inside_square_brackets = true; } else if (fmt[i] == ']') { is_inside_square_brackets = false; } else if (!is_inside_square_brackets && fmt[i] == ':') { auto const iterable_fmt = fmt.substr(0, i); auto const element_fmt = fmt.substr(i + 1, StringView::npos); return std::make_tuple(iterable_fmt, element_fmt); } } // If there isn't a colon cut point, all the input `fmt` string is the iterable // formatting return std::make_tuple(fmt, ""); } // -------------------------------------------------- template auto do_print_range( T&&, StringView, Config_ const& ) -> typename std::enable_if::value, PrintingNode>::type { ICECREAM_UNREACHABLE; return PrintingNode(""); } template auto do_print_range( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if::value , PrintingNode>::type { auto range_fmt = StringView{}; auto elements_fmt = StringView{}; std::tie(range_fmt, elements_fmt) = split_range_fmt_string(fmt); auto const mb_slice = Slice::build(range_fmt); if (!mb_slice) { return PrintingNode(""); } auto mb_slice_functor = maybe_make_slice_functor(value, *mb_slice); // If there was any error while creating the SliceFunctor if (mb_slice_functor.index() == 0) { return PrintingNode(get(mb_slice_functor)); } auto slice_functor = get>(mb_slice_functor); auto children = std::vector{}; auto mb_element = slice_functor(); while (mb_element) { children.push_back(make_printing_branch(*mb_element, elements_fmt, config)); mb_element = slice_functor(); } auto const opening = range_fmt.empty() ? "[" : range_fmt.to_string() + "->["; return PrintingNode(opening, ", ", "]", std::move(children)); } // Print all elements of a range template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if< ( is_range::value // it is range && !is_baseline_printable::value // but it is not printable by baseline strategies && !bypass_baseline_printing::value // and it hasn't its own strategy ) || ( // Except by arrays of anything other than character, which are baseline // printable, but should be printed by this strategy instead. std::is_array>::value && !is_c_string>::value ) , PrintingNode >::type { return do_print_range(std::forward(value), fmt, config); } // --------------------------------------------------------------------------------------------- // Print classes deriving from only std::exception and not from boost::exception template auto make_printing_branch( T&& value, StringView, Config_ const& ) -> typename std::enable_if< std::is_base_of>::value && !std::is_base_of>::value && !is_baseline_printable::value, PrintingNode >::type { return PrintingNode(value.what()); } // Print classes deriving from both std::exception and boost::exception template auto make_printing_branch( T&& value, StringView, Config_ const& ) -> typename std::enable_if< std::is_base_of>::value && std::is_base_of>::value && !is_baseline_printable::value, PrintingNode >::type { return PrintingNode( value.what() + std::string {"\n"} + boost::exception_detail::diagnostic_information_impl( &value, &value, true, true ) ); } #if defined(ICECREAM_DUMP_STRUCT_CLANG) static auto ds_this = static_cast(nullptr); static auto ds_delayed_structs = std::vector>>{}; static auto ds_fmt_ref = StringView{}; static auto ds_config_ref = static_cast(nullptr); static auto ds_call_count = int{0}; template auto parse_dump_struct(char const* format, T&& ...args) -> int; // Print classes using clang's __builtin_dump_struct (clang >= 15). template auto make_printing_branch( T&& value, StringView fmt, Config_ const& config ) -> typename std::enable_if::value, PrintingNode>::type { // If this is the outermost class being printed if (!ds_this) { auto branch_node = PrintingNode(""); ds_this = &branch_node; // Put `this` on scope to be assigned inside `parse_dump_struct` ds_fmt_ref = fmt; ds_config_ref = &config; __builtin_dump_struct(&value, &parse_dump_struct); // Print the outermost class // Loop on each class that is an internal attribute of the outermost class while (!ds_delayed_structs.empty()) { auto const delayed_struct = ds_delayed_structs.back(); ds_delayed_structs.pop_back(); // Put that attribute class on scope to `parse_struct_dump` ds_this = branch_node.find_leaf(delayed_struct.first); if (ds_this == nullptr) { std::cerr << "ICECREAM: Failure finding a child Tree, this should not have happened. " "Please report a bug on https://github.com/renatoGarcia/icecream-cpp/issues\n"; } delayed_struct.second(); // Print the class } ds_this = nullptr; ds_call_count = 0; return branch_node; } // Otherwise, the class at hand is an internal attribute from the outermost // class. If this is the case, this present calling of Tree constructor is // being made from inside a __builtin_dump_struct calling. So here we delay // the calling of __builtin_dump_struct to avoid a reentrant function call. else { auto const unique_id = "icecream_415a8a88-aa51-44d5-8ccb-377d953413ef_" + std::to_string(ds_call_count); ds_delayed_structs.emplace_back( unique_id, [&value]() { __builtin_dump_struct(&value, &parse_dump_struct); } ); ds_call_count += 1; // Return a placeholder node that will be replaced by the `if` above at the right time. return PrintingNode(unique_id); } } // Receives an argument from parse_dump_struct and builds a Tree using it. // // When clang doesn't know how to format a value within __builtin_dump_struct, it will // pass a pointer to that value instead of a reference. This function specialization // will handle that scenario, as it specializes on pointers. However, to distinguish // between actual pointer values and a "don't know how to format" value, the `deref` // boolean is used. template auto build_tree(bool deref, T* const& t) -> PrintingNode { if (deref) { return make_printing_branch(*t, ds_fmt_ref, *ds_config_ref); } else { return make_printing_branch(t, ds_fmt_ref, *ds_config_ref); } } // Receives an argument from parse_dump_struct and builds a Tree using it. template auto build_tree(bool, T const& t) -> PrintingNode { return make_printing_branch(t, ds_fmt_ref, *ds_config_ref); } // Receives all the variadic inputs from parse_dump_struct and returns a pair with the // argument name and its Tree representation. // This overload accepts zero, one, or two values from the variadic inputs. It is // here just so the code compiles and never will be called. When receiving these // number of inputs an argument to be printed will never be among them. inline auto build_name_tree_pair( bool, std::string const& = "", std::string const& = "" ) -> std::pair { ICECREAM_UNREACHABLE; return std::pair("", PrintingNode("")); } // Receives all the variadic inputs from parse_dump_struct and returns a pair with the // argument name and its Tree representation. // This overload will be called when starting the printing of an argument that is // itself a struct with other attributes. The empty `Tree` is a placeholder that will // be replaced when the actual tree is built. inline auto build_name_tree_pair( bool, std::string const&, std::string const&, std::string const& arg_name ) -> std::pair { return std::pair(arg_name, PrintingNode("")); } // Receives all the variadic inputs from parse_dump_struct and returns a pair with the // argument name and its Tree representation. template auto build_name_tree_pair( bool deref, std::string const&, std::string const&, std::string const& arg_name, T const& value ) -> std::pair { return std::pair(arg_name, build_tree(deref, value)); } // Each call to `parse_dump_struct` will deal with at most one attribute from the // struct being printed. This `attributes_stack` will hold the pairs (argument name, // Tree) constructed to each attribute until all of them are processed. static auto attributes_stack = std::vector>>{}; // When printing the attributes of a base class (inheritance), Clang will open and // close braces, similarly to when printing an attribute that is a struct // (composition). It is possible distinguish the two cases when at opening, but they // are identical when closing. This vector mark if the current context is inside an // inheritance attributes or a composition attributes. static auto is_inside_baseclass = std::vector{}; template int parse_dump_struct(char const* format_, T&&... args) { // Removes any left and right white spaces auto const trim = [](std::string const& str) -> std::string { auto const left = str.find_first_not_of(" \t\n"); if (left == std::string::npos) return ""; auto const right = str.find_last_not_of(" \t\n"); return str.substr(left, right-left+1); }; // Check if the last format specifier, the one related to the value to be printed, // means that clang doesn't know how to format that value. auto const knows_how_to_format = [](std::string const& str) -> bool { auto const left = str.find_last_of(" \t"); return str.substr(left+1) != "*%p"; }; auto const format = trim(format_); // When true, the next parse_dump_struct call will be the opening brace of a base // class scope. if (sizeof...(args) == 2) { is_inside_baseclass.push_back(true); } // When true, the next parse_dump_struct call will be the opening brace of a scope // printing the content of an atrribute that is a struct. else if (sizeof...(args) == 3) { is_inside_baseclass.push_back(false); attributes_stack.back().push_back( build_name_tree_pair(!knows_how_to_format(format), args...) ); // The next attributes will be from this struct attribute being printed. // Collect them all before merging on a Tree. attributes_stack.emplace_back(); } // When true, it is printing an actual value. Like "int i = 7". else if (sizeof...(args) == 4) { attributes_stack.back().push_back( build_name_tree_pair(!knows_how_to_format(format), args...) ); } // The closing brace of a baseclass attributes section else if (format.back() == '}' && is_inside_baseclass.back()) { is_inside_baseclass.pop_back(); } // The closing brace of either a struct attribute or the root struct being printed else if (format.back() == '}') { is_inside_baseclass.pop_back(); auto top_atrributes = std::move(attributes_stack.back()); attributes_stack.pop_back(); auto children = std::vector{}; for (auto& att : top_atrributes) { auto t_pair = std::vector{}; t_pair.push_back(PrintingNode(std::get<0>(att))); t_pair.push_back(std::move(std::get<1>(att))); children.emplace_back("", ": ", "", std::move(t_pair)); } auto built_tree = PrintingNode("{", ", ", "}", std::move(children)); if (attributes_stack.empty()) { *ds_this = std::move(built_tree); } else { std::get<1>(attributes_stack.back().back()) = std::move(built_tree); } } // The opening of the root struct being printed else if (sizeof...(args) == 1) { attributes_stack.emplace_back(); is_inside_baseclass.push_back(false); } return 0; } #endif // ICECREAM_DUMP_STRUCT_CLANG // -------------------------------------------------- is_printable template auto is_printable_impl(int) -> decltype( make_printing_branch( std::declval(), std::declval(), std::declval() ), std::true_type{} ); template auto is_printable_impl(...) -> std::false_type; // Check if IceCream can print the type T. template using is_printable = decltype(is_printable_impl(0)); // -------------------------------------------------- Icecream // Holds a pair of argument and formatting string. Created by the macro `IC_`. template struct FormattingArgument { std::string fmt; T&& value; }; template struct formatting_argumet_type_impl {using type = T;}; template struct formatting_argumet_type_impl&> {using type = T;}; template using formatting_argumet_type = typename formatting_argumet_type_impl::type; // If the type has an overload of to_string, call it. template auto call_to_string(T const& t) -> typename std::enable_if< has_to_string::value, std::string >::type { return to_string(t); } // If the type is a string like, just return it. template auto call_to_string(T const& t) -> typename std::enable_if< !has_to_string::value && ( is_character>::value || is_c_string>::value || is_std_string>::value || is_string_view>::value ), std::string >::type { return t; } // Just to compile, should not be called. template auto call_to_string(T const&) -> typename std::enable_if< !has_to_string::value && !( is_character>::value || is_c_string>::value || is_std_string>::value || is_string_view>::value ), std::string >::type { ICECREAM_UNREACHABLE; return ""; } // Would be better if this function could be on calling site as a lambda // function, however older compilers (gcc<=9 and clang<=10 for example) fail // to see a parameter pack expansion when inside of a lambda. template auto concat_fmt(T const& t, std::string& fmt, size_t& i_arg, size_t n_args) -> void { ++i_arg; if (i_arg < n_args) { fmt += call_to_string(t); } } // Returns a FormattingArgument where the value type is the same as the type // of the last `args`, and the formatting string is the concatenation of all // arguments except by the last. template auto make_formatting_argument( Ts&&... args ) -> FormattingArgument>::type> { auto constexpr value_idx = sizeof...(Ts) - 1; using T = typename std::tuple_element>::type; auto i_arg = size_t{0}; auto fmt = std::string{}; // Build `fmt` as the concatenation of all args, except by the last, // after converting them to a string through `call_to_string`. (void) std::initializer_list{ (concat_fmt(args, fmt, i_arg, sizeof...(Ts)), 0)... }; T value = std::get(std::forward_as_tuple(std::forward(args)...)); return FormattingArgument{fmt, std::forward(value)}; } // Holds a triple of argument name, formatting string, and value. Those are // all the info required to print an argument. template struct PrintingArgument { StringView name; StringView fmt; T&& value; }; // Print a forest that fits into a single line. inline auto print_one_line_forest( StringView prefix, StringView context, StringView delimiter, std::vector> const& forest ) -> std::string { auto result = prefix.to_string(); if (!context.empty()) { result += context.to_string() + delimiter.to_string(); } for (auto it = forest.begin(); it != forest.end(); ++it) { auto const& arg_name = std::get<0>(*it); auto const& tree = std::get<1>(*it); result += arg_name.to_string() + ": " + tree.print(); if (it+1 != forest.end()) { result += ", "; } } return result; } // Print a forest that will be broken on multiple lines. inline auto print_multi_line_forest( StringView prefix, StringView context, std::vector> const& forest, size_t const line_wrap_width ) -> std::string { auto result = prefix.to_string(); if (!context.empty()) { result += context.to_string(); } result += + "\n"; for (auto it = forest.begin(); it != forest.end(); ++it) { auto const& arg_name = std::get<0>(*it); auto const& tree = std::get<1>(*it); result += std::string(Config_::INDENT_BASE, ' ') + arg_name.to_string() + ": "; if (count_utf8_code_point(result) + tree.code_point_length() < line_wrap_width) { result += tree.print(); } else { result += tree.print(2, line_wrap_width); } if (it+1 != forest.end()) { result += ",\n"; } } return result; } template auto build_forest( Config_ const& config, PrintingArgument&... args ) -> std::vector> { auto forest = std::vector>{}; (void) std::initializer_list{ ( (void) forest.emplace_back( args.name, make_printing_branch(args.value, args.fmt, config) ), 0 )... }; return forest; } template auto print_args( Config_ const& config, StringView file, int line, StringView function, PrintingArgument... args ) -> typename std::enable_if< detail::conjunction...>::value >::type { #if defined(ICECREAM_DISABLE) return; #endif if (!config.is_enabled()) return; auto const prefix = config.prefix()(); auto const context = [&]() -> std::string { // If used an empty IC macro, i.e.: IC(). if (config.include_context()) { #if defined(_MSC_VER) auto const n = file.rfind('\\'); #else auto const n = file.rfind('/'); #endif return file.substr(n+1).to_string() + ":" + std::to_string(line) + " in \"" + function.to_string() + '"'; } else { return ""; } }(); auto const delimiter = config.context_delimiter(); auto const forest = build_forest(config, args...); // The number of codepoints used if the whole forest would be printed in an one // line. auto const one_line_forest_n_code_points = [&]() -> size_t { auto n = count_utf8_code_point(prefix); for (auto const& name_tree : forest) { n += count_utf8_code_point(std::get<0>(name_tree)) + count_utf8_code_point(": ") + std::get<1>(name_tree).code_point_length(); } // The ", " separators between variables. if (forest.size() > 1) { n += (forest.size() - 1) * count_utf8_code_point(", "); } return n; }(); if (one_line_forest_n_code_points > config.line_wrap_width()) { config.output()( config.output_transcoder()( print_multi_line_forest(prefix, context, forest, config.line_wrap_width()) ) ); } else { config.output()( config.output_transcoder()( print_one_line_forest(prefix, context, delimiter, forest) ) ); } config.output()(config.output_transcoder()("\n")); } // Handles the printing of a nullary IC() call. inline auto print_nullary( Config_ const& config, StringView file, int line, StringView function ) -> void { #if defined(ICECREAM_DISABLE) return; #endif if (!config.is_enabled()) return; auto const prefix = config.prefix()(); auto const context = [&]() -> std::string { #if defined(_MSC_VER) auto const n = file.rfind('\\'); #else auto const n = file.rfind('/'); #endif return file.substr(n+1).to_string() + ":" + std::to_string(line) + " in \"" + function.to_string() + '"'; }(); auto const str = prefix + context + "\n"; config.output()(config.output_transcoder()(str)); } /** This function will receive a string as "foo, bar, baz" and return a vector with * all the arguments split, such as ["foo", "bar", "baz"]. */ inline auto split_arguments(StringView all_names) -> std::vector { if (all_names.empty()) { return {}; } // Check if the '>' char is the closing of a template arguments listing. It will // be a template closing at `std::is_same::value` but not at `5 > 2` auto is_closing_template = [&all_names](size_t idx, size_t const last_idx) -> bool { auto curr_char = all_names[idx]; if (curr_char != '>' || idx == last_idx) { return false; } do { ++idx; curr_char = all_names[idx]; } while ( idx != last_idx && ( curr_char == ' ' || curr_char == '\t' || curr_char == '\n' || curr_char == '\r' || curr_char == '\f' || curr_char == '\v' ) ); return curr_char == ':' && idx != last_idx && all_names[idx + 1] == ':'; }; auto parenthesis_count = int{0}; auto angle_bracket_count = int{0}; auto curly_bracket_count = int{0}; auto is_within_double_quote = false; auto is_within_single_quote = false; // Parse the arguments string backward. It is easier this way to check if a '<' or // '>' character is either a comparison operator, or a opening and closing of // templates arguments. auto comma_idxs = std::vector{}; auto last_idx = all_names.size() - 1; for (auto idx = all_names.size(); idx-- > 0;) { auto const curr_char = all_names[idx]; auto const prev_char = idx > 0 ? all_names[idx-1] : '\0'; if (is_within_double_quote) { // if current char is the left double quote if (curr_char == '"' && prev_char != '\\') is_within_double_quote = false; } else if (is_within_single_quote) { // if current char is the left single quote if (curr_char == '\'' && prev_char != '\\') is_within_single_quote = false; } else if (curr_char == '"' && prev_char != '\\') { is_within_double_quote = true; } else if (curr_char == '\'' && prev_char != '\\') { is_within_single_quote = true; } // If it have found the comma separating two arguments else if ( curr_char == ',' && parenthesis_count == 0 && angle_bracket_count == 0 && curly_bracket_count == 0 ) { comma_idxs.push_back(idx); last_idx = idx - 1; } // It won't cut on a comma within parentheses, such as when the argument is a // function call, as in IC(foo(1, 2)) else if (curr_char == ')') { ++parenthesis_count; } else if (curr_char == '(') { --parenthesis_count; } else if (curr_char == '}') { ++curly_bracket_count; } else if (curr_char == '{') { --curly_bracket_count; } // It won't cut on a comma within a template argument list, such as in // IC(std::is_same::value) else if (is_closing_template(idx, last_idx)) { ++angle_bracket_count; } else if (curr_char == '<' && angle_bracket_count > 0) { --angle_bracket_count; } } auto result = split(all_names, {comma_idxs.rbegin(), comma_idxs.rend()}); for (auto& name : result) { name.trim(); } return result; } template auto get_value(T&& t) -> typename std::enable_if< !is_instantiation>::value, T&& >::type { return std::forward(t); } template auto get_value(T&& t) -> typename std::enable_if< is_instantiation>::value, decltype(t.value) >::type { return std::forward(t.value); } template auto get_fmt(T const&, StringView default_format) -> StringView { return default_format; } template auto get_fmt(FormattingArgument const& t, StringView) -> StringView { return t.fmt; } // The use of this struct instead of a free function is a needed hack because of the // trailing comma problem with __VA_ARGS__ expansion. A macro like: // // IC(...) print(__FILE__, __LINE__, ICECREAM_FUNCTION, #__VA_ARGS__, __VA_ARGS__) // // when used with no arguments would expand to one of: // // print("foo.cpp", 42, "void bar()", "",) // print("foo.cpp", 42, "void bar()", ,) // // A trailing comma is invalid when calling a function with parentheses, but allowed // when constructing an object with braces. // class Dispatcher { public: // Used by compilers that expand an empty __VA_ARGS__ in // Dispatcher{bla, #__VA_ARGS__} to Dispatcher{bla, ""} Dispatcher( bool is_ic_apply, Config_ const& config, StringView file, int line, StringView function, StringView default_format, StringView arg_names ) : is_ic_apply_(is_ic_apply) , config_(config) , file_(file) , line_{line} , function_(function) , default_format_(default_format) , arg_names_(arg_names) {} // Used by compilers that expand an empyt __VA_ARGS__ in // Dispatcher{bla, #__VA_ARGS__} to Dispatcher{bla, } Dispatcher( bool is_ic_apply, Config_ const& config, StringView file, int line, StringView function, StringView default_format ) : Dispatcher(is_ic_apply, config, file, line, function, default_format, "") {} // Runs the Dispatcher and returns the same unique argument. // It is called when printing only one value, e.g.: IC(v0) template auto unary_run(T&& arg) -> T&& { this->dispatch(make_int_sequence<1>(), arg); return std::forward(arg); } // Runs the Dispatcher and returns nothing. // It is called when printing zero or multiple values, e.g.: IC(), IC(v0, v1) template auto unary_run(Ts&&... args) -> void { this->dispatch(make_int_sequence(), args...); } // Runs the Dispatcher and returns a tuple with all the arguments. // This method is used by the apply macro variants (IC_A and IC_FA). The returned // tuple is used to keep the arguments alive when printing them and applying them // afterwards to the function. lvalues will be stored as lvalues, rvalues will be // stored as values. This needs be this way, because otherwise a prvalue argument // if not stored as a value would be destructed before being applied to the // function. Look at the `ICECREAM_APPLY_` macro implementation. template auto tuple_run(Ts&&... args) -> std::tuple...> { this->dispatch(make_int_sequence(), args...); return std::tuple...>(std::forward(args)...); } private: template auto dispatch(int_sequence, Ts&&... args) -> void { // Pick the name of an IC macro's "to be printed" argument. Usually that would // just return the argument string itself. However, when using the IC_ macro // to set a formatting string to an argument, all that information will be // mixed on that `IC_` call as a whole, and the argument name will need to be // extracted from there. auto pick_argument_name = [](StringView ic_argument) -> StringView { constexpr char prefix[] = "IC_("; constexpr auto n_prefix = sizeof(prefix); if (ic_argument.find(prefix) == StringView::npos) { return ic_argument; } else { auto const count = ic_argument.size() - n_prefix - 1; auto const fmt_args = split_arguments(ic_argument.substr(n_prefix, count)); return fmt_args.back(); } }; auto arg_names = std::vector{}; for (auto const& argument : split_arguments(this->arg_names_)) { arg_names.push_back(pick_argument_name(argument)); } if (this->is_ic_apply_) { // Remove the callable name from the arguments arg_names.erase(arg_names.begin()); } if (sizeof...(Ts) == 0 && !this->is_ic_apply_) { // Even if it has no arguments (besides the callable name), an `IC_A` // macro isn't a nullary `IC()` call. print_nullary(config_, file_, line_, function_); } else { print_args( config_, file_, line_, function_, PrintingArgument>{ arg_names.at(N), get_fmt(args, this->default_format_), get_value(std::forward(args)) }... ); } } bool is_ic_apply_; Config_ const& config_; StringView file_; int line_; StringView function_; StringView default_format_; StringView arg_names_; }; // --------------------------------------------------- Range View template struct RangeViewArgs { Optional mb_name_; Proj proj_; std::string elements_fmt_; Optional mb_slice_; Config_ const* config_ = nullptr; int line_; std::string src_location_; std::string file_; std::string function_; RangeViewArgs(Optional const& name, std::string const& fmt, Proj&& proj) : mb_name_(name) , proj_(proj) { auto view_fmt = StringView{}; auto elements_fmt = StringView{}; std::tie(view_fmt, elements_fmt) = split_range_fmt_string(fmt); this->elements_fmt_ = elements_fmt.to_string(); this->mb_slice_ = Slice::build(view_fmt); } auto complete( Config_& config, int line, std::string file, std::string function #if defined(ICECREAM_SOURCE_LOCATION) ,std::source_location const location = std::source_location::current() #endif ) -> RangeViewArgs& { this->config_ = &config; this->line_ = line; this->file_ = file; this->function_ = function; #if defined(ICECREAM_SOURCE_LOCATION) (void)line; this->src_location_ = std::to_string(location.line()) + ":" + std::to_string(location.column()); #else this->src_location_ = std::to_string(line); #endif return *this; } }; template auto IC_FV_( std::string const& fmt, std::string const& name, Proj&& proj = Identity{} ) -> RangeViewArgs { return RangeViewArgs(name, fmt, std::forward(proj)); } template auto IC_FV_( std::string const& fmt, Proj&& proj = Identity{} ) -> typename std::enable_if< !is_string_convertible::value, RangeViewArgs >::type { return RangeViewArgs({}, fmt, std::forward(proj)); } template auto IC_V_( std::string const& name, Proj&& proj = Identity{} ) -> RangeViewArgs { return RangeViewArgs(name, "", std::forward(proj)); } template auto IC_V_( Proj&& proj = Identity{} ) -> typename std::enable_if< !is_string_convertible::value, RangeViewArgs >::type { return RangeViewArgs({}, "", std::forward(proj)); } template struct RangeView { std::string name; Proj proj; Optional mb_slice; std::string elements_fmt; Config_ const& config; int line; std::string file; std::string function; int current_idx = 0; ptrdiff_t start; ptrdiff_t stop; ptrdiff_t step; Optional slice_error; explicit RangeView(RangeViewArgs par) : name( par.mb_name_ ? *par.mb_name_ : std::string{"range_view_"} + par.src_location_ ) , proj(par.proj_) , mb_slice(par.mb_slice_) , elements_fmt(par.elements_fmt_) , config(*par.config_) , line(par.line_) , file(par.file_) , function(par.function_) {} auto normalize_slice() -> void { if (!this->mb_slice) { this->slice_error = std::string{""}; return; } auto const mb_start = this->mb_slice->start; auto const mb_stop = this->mb_slice->stop; auto const mb_step = this->mb_slice->step; if ((mb_start && *mb_start < 0) || (mb_stop && *mb_stop < 0)) { this->slice_error = std::string{ "" }; } if (mb_step && *mb_step < 0) { this->slice_error = std::string{""}; } this->start = mb_start ? *mb_start : 0; this->stop = mb_stop ? *mb_stop : std::numeric_limits::max(); this->step = mb_step ? *mb_step : 1; } // Will be called by the operator `Range | IC_VIEW_`. Here we will know what is the // ranges and we can inspect its capabilities. template auto normalize_slice(R&& range) -> void { if (!this->mb_slice) { this->slice_error = std::string{""}; return; } auto const mb_size = maybe_get_size(range); auto const mb_start = this->mb_slice->start; auto const mb_stop = this->mb_slice->stop; auto const mb_step = this->mb_slice->step; if (!mb_size && ((mb_start && *mb_start < 0) || (mb_stop && *mb_stop < 0))) { this->slice_error = std::string{""}; } if (mb_step && *mb_step < 0) { this->slice_error = std::string{""}; } auto normalize_idx = [&](ptrdiff_t idx) -> ptrdiff_t { if (mb_size) { auto const range_size = static_cast(*mb_size); idx = (idx >= 0) ? idx : idx + range_size; return (idx < 0) ? 0 : idx; } else { return idx; } }; this->start = mb_start ? normalize_idx(*mb_start) : 0; this->stop = mb_stop ? normalize_idx(*mb_stop) : std::numeric_limits::max(); this->step = mb_step ? *mb_step : 1; } template auto operator()(T&& element) -> T&& { auto const idx = this->current_idx++; auto const arg_name = this->name + "[" + std::to_string(idx) + "]"; auto dispatcher = Dispatcher{ false, this->config, this->file, this->line, this->function, this->elements_fmt, arg_name }; if (this->slice_error) { dispatcher.unary_run(*this->slice_error); } else if ( this->start <= idx && idx < this->stop && ((idx - this->start) % this->step) == 0 ) { using TConst = typename std::add_const>::type; dispatcher.unary_run(this->proj(const_cast(element))); } return std::forward(element); } }; #if defined(ICECREAM_LIB_RANGES) // View | IC_V template requires std::ranges::view auto operator|(T&& t, RangeViewArgs view_args) { auto rv = RangeView(view_args); rv.normalize_slice(t); return std::forward(t) | std::views::transform(std::move(rv)); } // PartialView | IC_V template requires std::ranges::view> auto operator|(T&& t, RangeViewArgs view_args) { auto rv = RangeView(view_args); rv.normalize_slice(); return std::forward(t) | std::views::transform(std::move(rv)); } // IC_V | PartialView template requires std::ranges::view> auto operator|(RangeViewArgs view_args, T&& t) { auto rv = RangeView(view_args); rv.normalize_slice(); return std::views::transform(std::move(rv)) | std::forward(t); } // Range | IC_V // Here we don't know yet if we are in a Range-v3 or STL Ranges pipeline. template requires (!std::ranges::view && !std::ranges::view>) auto operator|(T& t, RangeViewArgs view_args) -> std::pair> { return {t, std::move(view_args)}; } // Pair (Range, IC_V) | PartialView template requires std::ranges::view> auto operator|(std::pair> t0, T1&& t1) { auto rv = RangeView(t0.second); rv.normalize_slice(t0.first); return t0.first | std::views::transform(std::move(rv)) | std::forward(t1); } #endif #if defined(ICECREAM_RANGE_V3_ENABLED) // View | IC_V template auto operator|(T&& t, RangeViewArgs view_args) -> typename std::enable_if< std::is_base_of::value, decltype(std::forward(t) | rv3v::transform(std::declval&>())) >::type { auto rv = RangeView(view_args); rv.normalize_slice(t); return std::forward(t) | rv3v::transform(std::move(rv)); } // PartialView | IC_V template auto operator|(T&& t, RangeViewArgs view_args) -> typename std::enable_if< std::is_base_of>::value, decltype(std::forward(t) | rv3v::transform(std::declval&>())) >::type { auto rv = RangeView(view_args); rv.normalize_slice(); return std::forward(t) | rv3v::transform(std::move(rv)); } // IC_V | PartialView template auto operator|(RangeViewArgs view_args, T&& t) -> typename std::enable_if< std::is_base_of>::value, decltype(rv3v::transform(std::declval&>()) | std::forward(t)) >::type { auto rv = RangeView(view_args); rv.normalize_slice(); return rv3v::transform(std::move(rv)) | std::forward(t); } // Range | IC_V // Here we don't know yet if we are in a Range-v3 or STL Ranges pipeline. template auto operator|(T& t, RangeViewArgs view_args) -> typename std::enable_if< !std::is_base_of::value && !std::is_base_of>::value, std::pair> >::type { return {t, std::move(view_args)}; } // Pair (Range, IC_V) | PartialView template auto operator|(std::pair> t0, T1&& t1) -> typename std::enable_if< std::is_base_of>::value, decltype(t0.first | rv3v::transform(std::declval&>()) | std::forward(t1)) >::type { auto rv = RangeView(t0.second); rv.normalize_slice(t0.first); return t0.first | rv3v::transform(std::move(rv)) | std::forward(t1); } #endif }} // namespace icecream::detail namespace { auto& icecream_private_config_5f803a3bcdb4 = icecream::detail::Config_::global(); auto& icecream_public_config_5f803a3bcdb4 = static_cast<::icecream::Config&>(icecream_private_config_5f803a3bcdb4); inline void silenceWarning() { (void)icecream_public_config_5f803a3bcdb4; } } #if defined(_MSC_VER) #pragma warning(pop) #endif #endif // ICECREAM_HPP_INCLUDED