From f898c19aca4136b314140baa74de38dd6b8b6468 Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Tue, 19 Apr 2022 14:14:45 +0200 Subject: [PATCH] Add apply*() functions (visitor pattern) Add 3 variants of apply*() functions that invoke a given callable with the supplied arguments and the stored JSON value. * void apply(Fn, Args...): ... * void apply_cb(ResultCallback, Fn, Args...): ... * R apply_r(Fn, Args...): ... --- docs/mkdocs/docs/home/exceptions.md | 22 + include/nlohmann/detail/meta/cpp_future.hpp | 40 ++ include/nlohmann/detail/meta/invoke.hpp | 120 ++++ include/nlohmann/detail/meta/type_traits.hpp | 28 +- include/nlohmann/detail/placeholders.hpp | 24 + include/nlohmann/json.hpp | 266 ++++++++ single_include/nlohmann/json.hpp | 484 +++++++++++++- tests/src/unit-apply.cpp | 661 +++++++++++++++++++ 8 files changed, 1625 insertions(+), 20 deletions(-) create mode 100644 include/nlohmann/detail/meta/invoke.hpp create mode 100644 include/nlohmann/detail/placeholders.hpp create mode 100644 tests/src/unit-apply.cpp diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 666d225bd..2f2e107f1 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -733,6 +733,28 @@ The dynamic type of the object cannot be represented in the requested serializat Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}` +### json.exception.type_error.318 + +The given callable cannot be invoked with the stored JSON value and the arguments provided. + +!!! failure "Example message" + + Calling `apply()` on a numeric JSON value with an unary non-member function accepting a string: + ``` + [json.exception.type_error.318] cannot invoke callable with JSON value of type number + ``` + +### json.exception.type_error.319 + +The result callback cannot be invoked with the callback argument, in case of a non-static member function pointer callback, and the result of the invocation of the callable. + +!!! failure "Example message" + + Calling `apply_cb()` with an unary non-member function result callback when the result of the invocation of the callable is not convertible to the argument type of the result callback: + ``` + [json.exception.type_error.319] cannot invoke callback + ``` + ## Out of range This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance in case of array indices or nonexisting object keys. diff --git a/include/nlohmann/detail/meta/cpp_future.hpp b/include/nlohmann/detail/meta/cpp_future.hpp index ef2da370d..66243a822 100644 --- a/include/nlohmann/detail/meta/cpp_future.hpp +++ b/include/nlohmann/detail/meta/cpp_future.hpp @@ -136,6 +136,46 @@ using index_sequence_for = make_index_sequence; #endif +#ifdef JSON_HAS_CPP_17 + +// the following utilities are natively available in C++17 +using std::conjunction; +using std::disjunction; +using std::negation; + +using std::as_const; + +#else + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction +: std::conditional, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/disjunction +template struct disjunction : std::false_type {}; +template struct disjunction : B {}; +template +struct disjunction +: std::conditional>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > {}; + +// https://en.cppreference.com/w/cpp/utility/as_const +template +constexpr typename std::add_const::type& as_const(T& t) noexcept +{ + return t; +} + +template +void as_const(const T&&) = delete; + +#endif + // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; diff --git a/include/nlohmann/detail/meta/invoke.hpp b/include/nlohmann/detail/meta/invoke.hpp new file mode 100644 index 000000000..cf42a671f --- /dev/null +++ b/include/nlohmann/detail/meta/invoke.hpp @@ -0,0 +1,120 @@ +#pragma once + +#ifdef JSON_HAS_CPP_17 + #include // invoke +#endif +#include // invoke_result, is_base_of, is_function, is_invocable, is_object, is_same, remove_reference +#include // declval, forward + +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ + +#ifdef JSON_HAS_CPP_17 + +// the following utilities are natively available in C++17 +using std::invoke; +using std::invoke_result; +using std::is_invocable; + +#else + +// invoke_impl derived from the C++ standard draft [func.require] for qslib (proprietary) +// modified to use standard library type traits and utilities where possible + +namespace internal +{ + +template()(std::forward(std::declval())...))> +auto invoke_impl(Fn && f, Args && ...args) -> decltype(f(std::forward(args)...)) +{ + return f(std::forward(args)...); +}; + +template < typename T, typename U, typename V, typename... Args, enable_if_t < + std::is_function::value + && std::is_base_of::type>::value, int > = 0 > +auto invoke_impl(T U::*f, V && v, Args && ...args) -> decltype((v.*f)(std::forward(args)...)) +{ + return (v.*f)(std::forward(args)...); +} + +template < typename T, typename U, typename V, typename... Args, enable_if_t < + std::is_function::value + && !std::is_base_of::type>::value, int > = 0 > +auto invoke_impl(T U::*f, V && v, Args && ...args) -> decltype(((*v).*f)(std::forward(args)...)) +{ + return ((*v).*f)(std::forward(args)...); +} + +template < typename T, typename U, typename V, typename... Args, enable_if_t < + std::is_object::value + && sizeof...(Args) == 0, int > = 0 > +auto invoke_impl(T U::*f, V && v, Args && ... /*unused*/) -> decltype((*v).*f) +{ + return (*v).*f; +} + +template +using detect_invocable = decltype(invoke_impl(std::declval(), std::declval()...)); + +// https://en.cppreference.com/w/cpp/types/result_of +template +struct invoke_result {}; + +template +struct invoke_result(), std::declval()...))), Fn, Args...> +{ + using type = decltype(invoke_impl(std::declval(), std::declval()...)); +}; + +} // namespace internal + +template +auto invoke(Fn&& f, Args&& ... args) -> decltype(internal::invoke_impl(std::forward(f), std::forward(args)...)) +{ + return internal::invoke_impl(std::forward(f), std::forward(args)...); +} + +template +using invoke_result = internal::invoke_result; + +template +using is_invocable = typename is_detected::type; + +#endif + +// used as a dummy argument +struct null_arg_t +{ + explicit null_arg_t() = default; +}; + +static constexpr null_arg_t null_arg{}; + +template +using is_null_arg = typename std::is_same, null_arg_t>::type; + +template +struct apply_value_or_arg +{ + using element_type = typename std::tuple_element::type; + using type = typename std::conditional::value, Value, element_type>::type; +}; + +template +using apply_invoke_result_t = typename detail::invoke_result::type...>::type; + +template +using apply_is_invocable = typename detail::is_invocable::type...>::type; + +} // namespace detail +} // namespace nlohmann diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 5e3ea3737..edc3c8e2a 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace nlohmann @@ -178,16 +179,6 @@ using actual_object_comparator_t = typename actual_object_comparator struct conjunction : std::true_type { }; -template struct conjunction : B { }; -template -struct conjunction -: std::conditional, B>::type {}; - -// https://en.cppreference.com/w/cpp/types/negation -template struct negation : std::integral_constant < bool, !B::value > { }; - // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). // This causes compile errors in e.g. clang 3.5 or gcc 4.9. @@ -564,6 +555,11 @@ struct is_ordered_map enum { value = sizeof(test(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) }; +// checks if a type is a basic_json_value placeholder +template +using is_basic_json_value_placeholder = typename std::is_same < + uncvref_t, uncvref_t>::type; + // to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > T conditional_static_cast(U value) @@ -577,5 +573,17 @@ T conditional_static_cast(U value) return value; } +// non-standard conditional version of as_const +template +constexpr typename std::conditional::type, T>::type& + conditional_as_const(T& t) noexcept +{ + return t; +} + +template +void conditional_as_const(const T&&) = delete; + } // namespace detail } // namespace nlohmann diff --git a/include/nlohmann/detail/placeholders.hpp b/include/nlohmann/detail/placeholders.hpp new file mode 100644 index 000000000..b502520cc --- /dev/null +++ b/include/nlohmann/detail/placeholders.hpp @@ -0,0 +1,24 @@ +#pragma once + +namespace nlohmann +{ +namespace detail +{ + +template +struct placeholder_t +{ + static constexpr int value = I; + + explicit placeholder_t() = default; +}; + +} // namespace detail + +namespace placeholders +{ + +static constexpr detail::placeholder_t < -1 > basic_json_value{}; + +} // namespace placeholders +} // namespace nlohmann diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 88be44b20..568a65964 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -62,6 +62,7 @@ SOFTWARE. #include // unique_ptr #include // accumulate #include // string, stoi, to_string +#include // forward_as_tuple, tuple #include // declval, forward, move, pair, swap #include // vector @@ -86,10 +87,12 @@ SOFTWARE. #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -207,6 +210,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// SAX interface type, see @ref nlohmann::json_sax using json_sax_t = json_sax; + // placeholder for the stored JSON value used in apply*() functions + static constexpr decltype(::nlohmann::placeholders::basic_json_value) value_placeholder + = ::nlohmann::placeholders::basic_json_value; + //////////////// // exceptions // //////////////// @@ -5120,8 +5127,267 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } /// @} + + private: + template::value, int> = 0> + static auto apply_resolve_placeholder(Arg && /*arg*/, Value && val) -> decltype(std::forward(val)) + { + return std::forward(val); + } + + template < typename Arg, typename Value, + detail::enable_if_t < !detail::is_basic_json_value_placeholder::value, int > = 0 > + static auto apply_resolve_placeholder(Arg && arg, Value&& /*val*/) -> decltype(std::forward(arg)) + { + return std::forward(arg); + } + + // invoke the result callback + template < typename ResultCallback, typename CallbackArg, typename R, + detail::enable_if_t < + detail::is_null_arg::value + && detail::is_null_arg::value, int > = 0 > + void apply_invoke_cb(ResultCallback && /*cb*/, CallbackArg && /*cb_arg*/, R&& /*r*/) const + {} + + template < typename ResultCallback, typename CallbackArg, typename R, + detail::enable_if_t < + !detail::is_null_arg::value + && detail::is_null_arg::value + && detail::is_invocable::value, int > = 0 > + void apply_invoke_cb(ResultCallback && cb, CallbackArg && /*cb_arg*/, R && r) const + { + detail::invoke(std::forward(cb), std::forward(r)); + } + + template < typename ResultCallback, typename CallbackArg, typename R, + detail::enable_if_t < + !detail::is_null_arg::value + && !detail::is_null_arg::value + && detail::is_invocable::value, int > = 0 > + void apply_invoke_cb(ResultCallback && cb, CallbackArg && cb_arg, R && r) const + { + detail::invoke(std::forward(cb), std::forward(cb_arg), std::forward(r)); + } + + template < typename ResultCallback, typename CallbackArg, typename R, + detail::enable_if_t < + (!detail::is_null_arg::value + && !detail::is_null_arg::value + && !detail::is_invocable::value) + || (!detail::is_null_arg::value + && detail::is_null_arg::value + && !detail::is_invocable::value), int > = 0 > + void apply_invoke_cb(ResultCallback && /*cb*/, CallbackArg && /*cb_arg*/, R&& /*r*/) const + { + JSON_THROW(type_error::create(319, detail::concat("cannot invoke callback"), this)); + } + + template + void apply_error() const + { + if (ConstThis) + { + JSON_THROW(type_error::create(318, detail::concat("cannot invoke callable with const JSON value of type ", type_name()), this)); + } + JSON_THROW(type_error::create(318, detail::concat("cannot invoke callable with JSON value of type ", type_name()), this)); + } + + // invoke function and possibly delegate result + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename Tuple, std::size_t... I, + detail::enable_if_t < detail::apply_is_invocable::value + && std::is_same, void>::value, int > = 0 > + void apply_invoke(Value && val, ResultCallback && /*cb*/, CallbackArg && /*cb_arg*/, Fn && f, Tuple && t, detail::index_sequence /*unused*/) const + { + detail::invoke(std::forward(f), apply_resolve_placeholder(std::get(t), std::forward(val))...); + } + + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename Tuple, std::size_t... I, + detail::enable_if_t < detail::apply_is_invocable::value + && !std::is_same, void>::value, int > = 0 > + void apply_invoke(Value && val, ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Tuple && t, detail::index_sequence /*unused*/) const + { + auto&& r = detail::invoke(std::forward(f), apply_resolve_placeholder(std::get(t), std::forward(val))...); + apply_invoke_cb(std::forward(cb), std::forward(cb_arg), std::forward(r)); + } + + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename Tuple, std::size_t... I, + detail::enable_if_t < !detail::apply_is_invocable::value, int > = 0 > + void apply_invoke(Value && /*val*/, ResultCallback && /*cb*/, CallbackArg && /*cb_arg*/, Fn && /*f*/, Tuple && /*t*/, detail::index_sequence /*unused*/) const + { + apply_error(); + } + + // convert arguments to tuple; insert basic_json_value placeholder if missing + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename FnArg, typename... Args, + detail::enable_if_t < std::is_member_pointer::value + && !detail::is_basic_json_value_placeholder::value + && !detail::disjunction...>::value, int > = 0 > + void apply_make_tuple(Value && val, ResultCallback && cb, CallbackArg && cb_arg, Fn && f, FnArg && f_arg, Args && ... args) const + { + apply_invoke( + std::forward(val), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward_as_tuple(f_arg, ::nlohmann::placeholders::basic_json_value, args...), + detail::make_index_sequence < 2 + sizeof...(args) > ()); + } + + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename... Args, + detail::enable_if_t < !std::is_member_pointer::value + && !detail::disjunction...>::value, int > = 0 > + void apply_make_tuple(Value && val, ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Args && ... args) const + { + apply_invoke( + std::forward(val), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward_as_tuple(::nlohmann::placeholders::basic_json_value, args...), + detail::make_index_sequence < 1 + sizeof...(args) > ()); + } + + template...>::value, int> = 0> + void apply_make_tuple(Value && val, ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Args && ... args) const + { + apply_invoke( + std::forward(val), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward_as_tuple(args...), + detail::make_index_sequence()); + } + + // dispatch based on stored value type + template + void apply_dispatch(ResultCallback&& cb, CallbackArg&& cb_arg, Fn&& f, Args&& ... args) const + { + switch (m_type) + { + case value_t::null: + return apply_make_tuple(nullptr, + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::object: + return apply_make_tuple(detail::conditional_as_const(*m_value.object), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::array: + return apply_make_tuple(detail::conditional_as_const(*m_value.array), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::string: + return apply_make_tuple(detail::conditional_as_const(*m_value.string), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::boolean: + return apply_make_tuple(detail::conditional_as_const(m_value.boolean), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::number_integer: + return apply_make_tuple(detail::conditional_as_const(m_value.number_integer), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::number_unsigned: + return apply_make_tuple(detail::conditional_as_const(m_value.number_unsigned), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::number_float: + return apply_make_tuple(detail::conditional_as_const(m_value.number_float), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::binary: + return apply_make_tuple(detail::conditional_as_const(*m_value.binary), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::discarded: + return apply_error(); + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + } + + public: + template + void apply(Fn&& f, Args&& ... args) + { + apply_dispatch( + detail::null_arg, detail::null_arg, + std::forward(f), std::forward(args)...); + } + + template + void apply(Fn&& f, Args&& ... args) const + { + apply_dispatch( + detail::null_arg, detail::null_arg, + std::forward(f), std::forward(args)...); + } + + template::value, int> = 0> + void apply_cb(ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Args && ... args) + { + apply_dispatch( + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + } + + template::value, int> = 0> + void apply_cb(ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Args && ... args) const + { + apply_dispatch( + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + } + + template < typename ResultCallback, typename Fn, typename... Args, detail::enable_if_t < + !std::is_member_pointer::value, int > = 0 > + void apply_cb(ResultCallback && cb, Fn && f, Args && ... args) + { + apply_dispatch( + std::forward(cb), detail::null_arg, + std::forward(f), std::forward(args)...); + } + + template < typename ResultCallback, typename Fn, typename... Args, detail::enable_if_t < + !std::is_member_pointer::value, int > = 0 > + void apply_cb(ResultCallback && cb, Fn && f, Args && ... args) const + { + apply_dispatch( + std::forward(cb), detail::null_arg, + std::forward(f), std::forward(args)...); + } + + template + R apply_r(Fn&& f, Args&& ... args) + { + R out; + apply_cb([&out](R && r) noexcept(noexcept(out = std::forward(r))) + { + out = std::forward(r); + }, std::forward(f), std::forward(args)...); + return out; + } + + template + R apply_r(Fn&& f, Args&& ... args) const + { + R out; + apply_cb([&out](R && r) noexcept(noexcept(out = std::forward(r))) + { + out = std::forward(r); + }, std::forward(f), std::forward(args)...); + return out; + } }; +#ifndef JSON_HAS_CPP_17 + + NLOHMANN_BASIC_JSON_TPL_DECLARATION + constexpr decltype(::nlohmann::placeholders::basic_json_value) NLOHMANN_BASIC_JSON_TPL::value_placeholder; // NOLINT(readability-redundant-declaration) + +#endif + /// @brief user-defined to_string function for JSON values /// @sa https://json.nlohmann.me/api/basic_json/to_string/ NLOHMANN_BASIC_JSON_TPL_DECLARATION diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 25b13dee1..f8052897f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -62,6 +62,7 @@ SOFTWARE. #include // unique_ptr #include // accumulate #include // string, stoi, to_string +#include // forward_as_tuple, tuple #include // declval, forward, move, pair, swap #include // vector @@ -3023,6 +3024,46 @@ using index_sequence_for = make_index_sequence; #endif +#ifdef JSON_HAS_CPP_17 + +// the following utilities are natively available in C++17 +using std::conjunction; +using std::disjunction; +using std::negation; + +using std::as_const; + +#else + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction +: std::conditional, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/disjunction +template struct disjunction : std::false_type {}; +template struct disjunction : B {}; +template +struct disjunction +: std::conditional>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > {}; + +// https://en.cppreference.com/w/cpp/utility/as_const +template +constexpr typename std::add_const::type& as_const(T& t) noexcept +{ + return t; +} + +template +void as_const(const T&&) = delete; + +#endif + // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; @@ -3136,6 +3177,32 @@ NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); // #include +// #include + + +namespace nlohmann +{ +namespace detail +{ + +template +struct placeholder_t +{ + static constexpr int value = I; + + explicit placeholder_t() = default; +}; + +} // namespace detail + +namespace placeholders +{ + +static constexpr detail::placeholder_t < -1 > basic_json_value{}; + +} // namespace placeholders +} // namespace nlohmann + // #include #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ @@ -3367,16 +3434,6 @@ using actual_object_comparator_t = typename actual_object_comparator struct conjunction : std::true_type { }; -template struct conjunction : B { }; -template -struct conjunction -: std::conditional, B>::type {}; - -// https://en.cppreference.com/w/cpp/types/negation -template struct negation : std::integral_constant < bool, !B::value > { }; - // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). // This causes compile errors in e.g. clang 3.5 or gcc 4.9. @@ -3753,6 +3810,11 @@ struct is_ordered_map enum { value = sizeof(test(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) }; +// checks if a type is a basic_json_value placeholder +template +using is_basic_json_value_placeholder = typename std::is_same < + uncvref_t, uncvref_t>::type; + // to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > T conditional_static_cast(U value) @@ -3766,6 +3828,18 @@ T conditional_static_cast(U value) return value; } +// non-standard conditional version of as_const +template +constexpr typename std::conditional::type, T>::type& + conditional_as_const(T& t) noexcept +{ + return t; +} + +template +void conditional_as_const(const T&&) = delete; + } // namespace detail } // namespace nlohmann @@ -13953,6 +14027,131 @@ class json_ref // #include +// #include + + +#ifdef JSON_HAS_CPP_17 + #include // invoke +#endif +#include // invoke_result, is_base_of, is_function, is_invocable, is_object, is_same, remove_reference +#include // declval, forward + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ + +#ifdef JSON_HAS_CPP_17 + +// the following utilities are natively available in C++17 +using std::invoke; +using std::invoke_result; +using std::is_invocable; + +#else + +// invoke_impl derived from the C++ standard draft [func.require] for qslib (proprietary) +// modified to use standard library type traits and utilities where possible + +namespace internal +{ + +template()(std::forward(std::declval())...))> +auto invoke_impl(Fn && f, Args && ...args) -> decltype(f(std::forward(args)...)) +{ + return f(std::forward(args)...); +}; + +template < typename T, typename U, typename V, typename... Args, enable_if_t < + std::is_function::value + && std::is_base_of::type>::value, int > = 0 > +auto invoke_impl(T U::*f, V && v, Args && ...args) -> decltype((v.*f)(std::forward(args)...)) +{ + return (v.*f)(std::forward(args)...); +} + +template < typename T, typename U, typename V, typename... Args, enable_if_t < + std::is_function::value + && !std::is_base_of::type>::value, int > = 0 > +auto invoke_impl(T U::*f, V && v, Args && ...args) -> decltype(((*v).*f)(std::forward(args)...)) +{ + return ((*v).*f)(std::forward(args)...); +} + +template < typename T, typename U, typename V, typename... Args, enable_if_t < + std::is_object::value + && sizeof...(Args) == 0, int > = 0 > +auto invoke_impl(T U::*f, V && v, Args && ... /*unused*/) -> decltype((*v).*f) +{ + return (*v).*f; +} + +template +using detect_invocable = decltype(invoke_impl(std::declval(), std::declval()...)); + +// https://en.cppreference.com/w/cpp/types/result_of +template +struct invoke_result {}; + +template +struct invoke_result(), std::declval()...))), Fn, Args...> +{ + using type = decltype(invoke_impl(std::declval(), std::declval()...)); +}; + +} // namespace internal + +template +auto invoke(Fn&& f, Args&& ... args) -> decltype(internal::invoke_impl(std::forward(f), std::forward(args)...)) +{ + return internal::invoke_impl(std::forward(f), std::forward(args)...); +} + +template +using invoke_result = internal::invoke_result; + +template +using is_invocable = typename is_detected::type; + +#endif + +// used as a dummy argument +struct null_arg_t +{ + explicit null_arg_t() = default; +}; + +static constexpr null_arg_t null_arg{}; + +template +using is_null_arg = typename std::is_same, null_arg_t>::type; + +template +struct apply_value_or_arg +{ + using element_type = typename std::tuple_element::type; + using type = typename std::conditional::value, Value, element_type>::type; +}; + +template +using apply_invoke_result_t = typename detail::invoke_result::type...>::type; + +template +using apply_is_invocable = typename detail::is_invocable::type...>::type; + +} // namespace detail +} // namespace nlohmann + // #include // #include @@ -18030,6 +18229,8 @@ class serializer } // namespace detail } // namespace nlohmann +// #include + // #include // #include @@ -18389,6 +18590,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// SAX interface type, see @ref nlohmann::json_sax using json_sax_t = json_sax; + // placeholder for the stored JSON value used in apply*() functions + static constexpr decltype(::nlohmann::placeholders::basic_json_value) value_placeholder + = ::nlohmann::placeholders::basic_json_value; + //////////////// // exceptions // //////////////// @@ -23302,8 +23507,267 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } /// @} + + private: + template::value, int> = 0> + static auto apply_resolve_placeholder(Arg && /*arg*/, Value && val) -> decltype(std::forward(val)) + { + return std::forward(val); + } + + template < typename Arg, typename Value, + detail::enable_if_t < !detail::is_basic_json_value_placeholder::value, int > = 0 > + static auto apply_resolve_placeholder(Arg && arg, Value&& /*val*/) -> decltype(std::forward(arg)) + { + return std::forward(arg); + } + + // invoke the result callback + template < typename ResultCallback, typename CallbackArg, typename R, + detail::enable_if_t < + detail::is_null_arg::value + && detail::is_null_arg::value, int > = 0 > + void apply_invoke_cb(ResultCallback && /*cb*/, CallbackArg && /*cb_arg*/, R&& /*r*/) const + {} + + template < typename ResultCallback, typename CallbackArg, typename R, + detail::enable_if_t < + !detail::is_null_arg::value + && detail::is_null_arg::value + && detail::is_invocable::value, int > = 0 > + void apply_invoke_cb(ResultCallback && cb, CallbackArg && /*cb_arg*/, R && r) const + { + detail::invoke(std::forward(cb), std::forward(r)); + } + + template < typename ResultCallback, typename CallbackArg, typename R, + detail::enable_if_t < + !detail::is_null_arg::value + && !detail::is_null_arg::value + && detail::is_invocable::value, int > = 0 > + void apply_invoke_cb(ResultCallback && cb, CallbackArg && cb_arg, R && r) const + { + detail::invoke(std::forward(cb), std::forward(cb_arg), std::forward(r)); + } + + template < typename ResultCallback, typename CallbackArg, typename R, + detail::enable_if_t < + (!detail::is_null_arg::value + && !detail::is_null_arg::value + && !detail::is_invocable::value) + || (!detail::is_null_arg::value + && detail::is_null_arg::value + && !detail::is_invocable::value), int > = 0 > + void apply_invoke_cb(ResultCallback && /*cb*/, CallbackArg && /*cb_arg*/, R&& /*r*/) const + { + JSON_THROW(type_error::create(319, detail::concat("cannot invoke callback"), this)); + } + + template + void apply_error() const + { + if (ConstThis) + { + JSON_THROW(type_error::create(318, detail::concat("cannot invoke callable with const JSON value of type ", type_name()), this)); + } + JSON_THROW(type_error::create(318, detail::concat("cannot invoke callable with JSON value of type ", type_name()), this)); + } + + // invoke function and possibly delegate result + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename Tuple, std::size_t... I, + detail::enable_if_t < detail::apply_is_invocable::value + && std::is_same, void>::value, int > = 0 > + void apply_invoke(Value && val, ResultCallback && /*cb*/, CallbackArg && /*cb_arg*/, Fn && f, Tuple && t, detail::index_sequence /*unused*/) const + { + detail::invoke(std::forward(f), apply_resolve_placeholder(std::get(t), std::forward(val))...); + } + + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename Tuple, std::size_t... I, + detail::enable_if_t < detail::apply_is_invocable::value + && !std::is_same, void>::value, int > = 0 > + void apply_invoke(Value && val, ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Tuple && t, detail::index_sequence /*unused*/) const + { + auto&& r = detail::invoke(std::forward(f), apply_resolve_placeholder(std::get(t), std::forward(val))...); + apply_invoke_cb(std::forward(cb), std::forward(cb_arg), std::forward(r)); + } + + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename Tuple, std::size_t... I, + detail::enable_if_t < !detail::apply_is_invocable::value, int > = 0 > + void apply_invoke(Value && /*val*/, ResultCallback && /*cb*/, CallbackArg && /*cb_arg*/, Fn && /*f*/, Tuple && /*t*/, detail::index_sequence /*unused*/) const + { + apply_error(); + } + + // convert arguments to tuple; insert basic_json_value placeholder if missing + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename FnArg, typename... Args, + detail::enable_if_t < std::is_member_pointer::value + && !detail::is_basic_json_value_placeholder::value + && !detail::disjunction...>::value, int > = 0 > + void apply_make_tuple(Value && val, ResultCallback && cb, CallbackArg && cb_arg, Fn && f, FnArg && f_arg, Args && ... args) const + { + apply_invoke( + std::forward(val), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward_as_tuple(f_arg, ::nlohmann::placeholders::basic_json_value, args...), + detail::make_index_sequence < 2 + sizeof...(args) > ()); + } + + template < bool ConstThis, typename Value, typename ResultCallback, typename CallbackArg, typename Fn, typename... Args, + detail::enable_if_t < !std::is_member_pointer::value + && !detail::disjunction...>::value, int > = 0 > + void apply_make_tuple(Value && val, ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Args && ... args) const + { + apply_invoke( + std::forward(val), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward_as_tuple(::nlohmann::placeholders::basic_json_value, args...), + detail::make_index_sequence < 1 + sizeof...(args) > ()); + } + + template...>::value, int> = 0> + void apply_make_tuple(Value && val, ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Args && ... args) const + { + apply_invoke( + std::forward(val), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward_as_tuple(args...), + detail::make_index_sequence()); + } + + // dispatch based on stored value type + template + void apply_dispatch(ResultCallback&& cb, CallbackArg&& cb_arg, Fn&& f, Args&& ... args) const + { + switch (m_type) + { + case value_t::null: + return apply_make_tuple(nullptr, + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::object: + return apply_make_tuple(detail::conditional_as_const(*m_value.object), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::array: + return apply_make_tuple(detail::conditional_as_const(*m_value.array), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::string: + return apply_make_tuple(detail::conditional_as_const(*m_value.string), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::boolean: + return apply_make_tuple(detail::conditional_as_const(m_value.boolean), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::number_integer: + return apply_make_tuple(detail::conditional_as_const(m_value.number_integer), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::number_unsigned: + return apply_make_tuple(detail::conditional_as_const(m_value.number_unsigned), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::number_float: + return apply_make_tuple(detail::conditional_as_const(m_value.number_float), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::binary: + return apply_make_tuple(detail::conditional_as_const(*m_value.binary), + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + case value_t::discarded: + return apply_error(); + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + } + + public: + template + void apply(Fn&& f, Args&& ... args) + { + apply_dispatch( + detail::null_arg, detail::null_arg, + std::forward(f), std::forward(args)...); + } + + template + void apply(Fn&& f, Args&& ... args) const + { + apply_dispatch( + detail::null_arg, detail::null_arg, + std::forward(f), std::forward(args)...); + } + + template::value, int> = 0> + void apply_cb(ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Args && ... args) + { + apply_dispatch( + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + } + + template::value, int> = 0> + void apply_cb(ResultCallback && cb, CallbackArg && cb_arg, Fn && f, Args && ... args) const + { + apply_dispatch( + std::forward(cb), std::forward(cb_arg), + std::forward(f), std::forward(args)...); + } + + template < typename ResultCallback, typename Fn, typename... Args, detail::enable_if_t < + !std::is_member_pointer::value, int > = 0 > + void apply_cb(ResultCallback && cb, Fn && f, Args && ... args) + { + apply_dispatch( + std::forward(cb), detail::null_arg, + std::forward(f), std::forward(args)...); + } + + template < typename ResultCallback, typename Fn, typename... Args, detail::enable_if_t < + !std::is_member_pointer::value, int > = 0 > + void apply_cb(ResultCallback && cb, Fn && f, Args && ... args) const + { + apply_dispatch( + std::forward(cb), detail::null_arg, + std::forward(f), std::forward(args)...); + } + + template + R apply_r(Fn&& f, Args&& ... args) + { + R out; + apply_cb([&out](R && r) noexcept(noexcept(out = std::forward(r))) + { + out = std::forward(r); + }, std::forward(f), std::forward(args)...); + return out; + } + + template + R apply_r(Fn&& f, Args&& ... args) const + { + R out; + apply_cb([&out](R && r) noexcept(noexcept(out = std::forward(r))) + { + out = std::forward(r); + }, std::forward(f), std::forward(args)...); + return out; + } }; +#ifndef JSON_HAS_CPP_17 + + NLOHMANN_BASIC_JSON_TPL_DECLARATION + constexpr decltype(::nlohmann::placeholders::basic_json_value) NLOHMANN_BASIC_JSON_TPL::value_placeholder; // NOLINT(readability-redundant-declaration) + +#endif + /// @brief user-defined to_string function for JSON values /// @sa https://json.nlohmann.me/api/basic_json/to_string/ NLOHMANN_BASIC_JSON_TPL_DECLARATION diff --git a/tests/src/unit-apply.cpp b/tests/src/unit-apply.cpp new file mode 100644 index 000000000..d01ee1dc2 --- /dev/null +++ b/tests/src/unit-apply.cpp @@ -0,0 +1,661 @@ +#include "doctest_compatibility.h" + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wimplicit-int-float-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data +#include +using nlohmann::json; + +#include + +#if JSON_HAS_RANGES + // JSON_HAS_CPP_20 (magic keyword; do not remove) + #include +#endif + +// MSSTL defines as_const in the global namespace :facepalm: +template +static auto const_(Args&& ... args) -> decltype(nlohmann::detail::as_const(std::forward(args)...)) +{ + return nlohmann::detail::as_const(std::forward(args)...); +} + +static void array_push_back(json::array_t& arr, json val) +{ + arr.emplace_back(std::move(val)); +} + +static void array_push_front(json val, json::array_t& arr) +{ + arr.emplace(arr.begin(), std::move(val)); +} + +struct foo +{ + int bar = 0; + void set_bar(int i) noexcept + { + bar = i; + } + + static int static_bar; + static void static_set_bar(int i) noexcept + { + static_bar = i; + } +}; + +int foo::static_bar = 0; + +struct functor +{ + int arg; + int value = 0; + + explicit functor(int arg_ = 0) noexcept : arg(arg_) {} + + void operator()(int a, int b = 0, int c = 0) noexcept + { + switch (arg) + { + default: + case 0: + value = a; + break; + case 1: + value = b; + break; + case 2: + value = c; + break; + } + } +}; + +static int get_value(int i) noexcept +{ + return i; +} + +static int callback_value = 0; + +static void callback(int i) noexcept +{ + callback_value = i; +} + +struct not_an_int +{ + explicit not_an_int() = default; +}; + +static not_an_int get_not_an_int(int /*unused*/) +{ + return not_an_int{}; +} + +TEST_CASE("apply*() functions") +{ + SECTION("placeholder") + { + using nlohmann::placeholders::basic_json_value; + using nlohmann::detail::is_basic_json_value_placeholder; + CHECK(std::is_same::value); + CHECK(is_basic_json_value_placeholder::value); + CHECK_FALSE(is_basic_json_value_placeholder::value); + } + + SECTION("apply()") + { + SECTION("plain function") + { + SECTION("const") + { + const json j = json::array({"foo"}); + CHECK_THROWS_WITH_AS(j.apply(array_push_back, 42), + "[json.exception.type_error.318] cannot invoke callable with const JSON value of type array", + json::type_error&); + } + + SECTION("non-const") + { + SECTION("without explicit placeholder") + { + json j = json::array({"foo"}); + json j_expected = json::array({"foo", 42}); + + j.apply(array_push_back, 42); + + CHECK(j == j_expected); + } + + SECTION("with explicit placeholder") + { + json j = json::array({"foo"}); + json j_expected = json::array({42, "foo"}); + json j_expected2 = json::array({42, "foo", 24}); + + j.apply(array_push_front, 42, json::value_placeholder); + + CHECK(j == j_expected); + + j.apply(array_push_back, json::value_placeholder, 24); + + CHECK(j == j_expected2); + } + } + } + + SECTION("static member function pointer") + { + json j(42); + + SECTION("const (without explicit placeholder)") + { + foo::static_bar = 0; + const_(j).apply(&foo::static_set_bar); + + CHECK(foo::static_bar == 42); + CHECK(j == 42); + } + + SECTION("const (with explicit placeholder)") + { + foo::static_bar = 0; + const_(j).apply(&foo::static_set_bar, json::value_placeholder); + + CHECK(foo::static_bar == 42); + CHECK(j == 42); + } + + SECTION("non-const (without explicit placeholder)") + { + foo::static_bar = 0; + j.apply(&foo::static_set_bar); + + CHECK(foo::static_bar == 42); + CHECK(j == 42); + } + + SECTION("non-const (with explicit placeholder)") + { + foo::static_bar = 0; + j.apply(&foo::static_set_bar, json::value_placeholder); + + CHECK(foo::static_bar == 42); + CHECK(j == 42); + } + } + + SECTION("non-static member function pointer") + { + json j(42); + + SECTION("const (without explicit placeholder)") + { + foo f; + + const_(j).apply(&foo::set_bar, f); + + CHECK(f.bar == 42); + CHECK(j == 42); + } + + SECTION("const (with explicit placeholder)") + { + foo f; + + const_(j).apply(&foo::set_bar, f, json::value_placeholder); + + CHECK(f.bar == 42); + CHECK(j == 42); + } + + SECTION("non-const (without explicit placeholder)") + { + foo f; + + j.apply(&foo::set_bar, f); + + CHECK(f.bar == 42); + CHECK(j == 42); + } + + SECTION("non-const (with explicit placeholder)") + { + foo f; + + j.apply(&foo::set_bar, f, json::value_placeholder); + + CHECK(f.bar == 42); + CHECK(j == 42); + } + } + + SECTION("non-static function member pointer (json::array_t::resize)") + { + json j = json::array(); + json j_expected = json::array({42, 42}); + + SECTION("const") + { + CHECK_THROWS_WITH_AS(const_(j).apply(static_cast(&json::array_t::resize), json::value_placeholder, 2, json(42)), + "[json.exception.type_error.318] cannot invoke callable with const JSON value of type array", + json::type_error&); + CHECK(j.empty()); + } + + SECTION("non-const (without explicit placeholder)") + { + CHECK_THROWS_WITH_AS( + j.apply(static_cast(&json::array_t::resize), 2, json(42)), + "[json.exception.type_error.318] cannot invoke callable with JSON value of type array", + json::type_error&); + CHECK(j.empty()); + } + + SECTION("non-const (with explicit placeholder)") + { + j.apply(static_cast(&json::array_t::resize), json::value_placeholder, 2, json(42)); + + CHECK(j == j_expected); + } + } + + SECTION("functor") + { + json j(42); + + SECTION("const (without explicit placeholder)") + { + functor f{0}; + const_(j).apply(f, -1, -2); + + CHECK(f.value == 42); + CHECK(j == 42); + } + + SECTION("const (with explicit placeholder)") + { + functor f{1}; + const_(j).apply(f, 0, json::value_placeholder, -2); + + CHECK(f.value == 42); + CHECK(j == 42); + } + + SECTION("non-const (without explicit placeholder)") + { + functor f{0}; + j.apply(f, -1, -2); + + CHECK(f.value == 42); + CHECK(j == 42); + } + + SECTION("non-const (with explicit placeholder)") + { + functor f{1}; + j.apply(f, 0, json::value_placeholder, -2); + + CHECK(f.value == 42); + CHECK(j == 42); + } + } + + SECTION("discarded JSON value") + { + json j(json::value_t::discarded); + + SECTION("const") + { + CHECK_THROWS_WITH_AS( + const_(j).apply(nlohmann::detail::null_arg), + "[json.exception.type_error.318] cannot invoke callable with const JSON value of type discarded", + json::type_error&); + } + + SECTION("non-const") + { + CHECK_THROWS_WITH_AS( + j.apply(nlohmann::detail::null_arg), + "[json.exception.type_error.318] cannot invoke callable with JSON value of type discarded", + json::type_error&); + } + } + } + + // apply_cb() (which apply_r() is based on) fails on Clang + // due to is_invocable not yielding true as expected + // while testing the invocable implementation Clang 3.5 crashed +#if !defined(__clang__) || (defined(__clang__) && __clang_major__ == 3 && __clang_minor__ < 6) + SECTION("apply_r()") + { + SECTION("value types") + { + SECTION("null") + { + json j; + + auto is_null = [](std::nullptr_t) noexcept + { + return true; + }; + + SECTION("const") + { + CHECK(j.is_null()); + CHECK(const_(j).apply_r(is_null)); + } + + SECTION("non-const") + { + CHECK(j.is_null()); + CHECK(j.apply_r(is_null)); + } + } + + SECTION("object") + { + json j{{"foo", 0}, {"bar", 42}}; + + auto get_bar = [](const json::object_t& obj) + { +#if JSON_USE_IMPLICIT_CONVERSIONS + return obj.at("bar"); +#else + return obj.at("bar").get(); +#endif + }; + + SECTION("const") + { + CHECK(j.is_object()); + CHECK(const_(j).apply_r(get_bar) == 42); + } + + SECTION("non-const") + { + CHECK(j.is_object()); + CHECK(j.apply_r(get_bar) == 42); + } + } + + SECTION("array") + { + json j{0, 1, 42, 3, 4}; + + auto get_2 = [](const json::array_t& arr) + { +#if JSON_USE_IMPLICIT_CONVERSIONS + return arr[2]; +#else + return arr[2].get(); +#endif + }; + + SECTION("const") + { + CHECK(j.is_array()); + CHECK(const_(j).apply_r(get_2) == 42); + } + + SECTION("non-const") + { + CHECK(j.is_array()); + CHECK(j.apply_r(get_2) == 42); + } + } + + SECTION("string") + { + json j("fourty two"); + + auto length = [](const json::string_t& str) noexcept + { + return str.size(); + }; + + SECTION("const") + { + CHECK(j.is_string()); + CHECK(const_(j).apply_r(length) == 10); + } + + SECTION("non-const") + { + CHECK(j.is_string()); + CHECK(j.apply_r(length) == 10); + } + } + + SECTION("boolean") + { + json j(false); + + auto negate = [](bool b) noexcept + { + return !b; + }; + + SECTION("const") + { + CHECK(j.is_boolean()); + CHECK(const_(j).apply_r(negate)); + } + + SECTION("non-const") + { + CHECK(j.is_boolean()); + CHECK(j.apply_r(negate)); + } + } + + SECTION("number_integer") + { + json j(-7); + + auto calc = [](json::number_integer_t i) noexcept + { + return i * i + i; + }; + + SECTION("const") + { + CHECK(j.is_number_integer()); + CHECK(const_(j).apply_r(calc) == 42); + } + + SECTION("non-const") + { + CHECK(j.is_number_integer()); + CHECK(j.apply_r(calc) == 42); + } + } + + SECTION("number_unsigned") + { + json j(static_cast(7)); + + auto calc = [](json::number_unsigned_t i) noexcept + { + return i * i - i; + }; + + SECTION("const") + { + CHECK(j.is_number_unsigned()); + CHECK(const_(j).apply_r(calc) == 42); + } + + SECTION("non-const") + { + CHECK(j.is_number_unsigned()); + CHECK(j.apply_r(calc) == 42); + } + } + + SECTION("number_float") + { + json j(6.480741); + + auto square = [](json::number_float_t f) noexcept + { + return f * f; + }; + + SECTION("const") + { + CHECK(j.is_number_float()); + CHECK(const_(j).apply_r(square) == doctest::Approx(42.0)); + } + + SECTION("non-const") + { + CHECK(j.is_number_float()); + CHECK(j.apply_r(square) == doctest::Approx(42.0)); + } + } + + SECTION("binary") + { + json j = json::binary(std::vector {0xC0, 0xFF, 0xEE}); + + auto get_1 = [](const json::binary_t& bin) noexcept + { + return bin[1]; + }; + + SECTION("const") + { + CHECK(j.is_binary()); + CHECK(const_(j).apply_r(get_1) == 0xFF); + } + + SECTION("non-const") + { + CHECK(j.is_binary()); + CHECK(j.apply_r(get_1) == 0xFF); + } + } + } + +#if JSON_HAS_RANGES + SECTION("std::ranges::min") + { + json j = json::array({5, 3, 4, 2}); + + SECTION("const (without explicit placeholder)") + { + CHECK(const_(j).apply_r(std::ranges::min) == 2); + } + + SECTION("const (with explicit placeholder)") + { + CHECK(const_(j).apply_r(std::ranges::min, json::value_placeholder) == 2); + } + + SECTION("non-const (without explicit placeholder)") + { + CHECK(j.apply_r(std::ranges::min) == 2); + } + + SECTION("non-const (with explicit placeholder)") + { + CHECK(j.apply_r(std::ranges::min, json::value_placeholder) == 2); + } + } +#endif + } + + SECTION("apply_cb()") + { + SECTION("plain function callback") + { + json j(42); + + SECTION("const") + { + callback_value = 0; + const_(j).apply_cb(callback, get_value); + + CHECK(callback_value == 42); + } + + SECTION("non-const") + { + callback_value = 0; + j.apply_cb(callback, get_value); + + CHECK(callback_value == 42); + } + + SECTION("exception") + { + CHECK_THROWS_WITH_AS( + j.apply_cb(callback, get_not_an_int), + "[json.exception.type_error.319] cannot invoke callback", + json::type_error&); + } + } + + SECTION("static member function pointer") + { + json j(42); + + SECTION("const") + { + foo::static_bar = 0; + const_(j).apply_cb(&foo::static_set_bar, get_value); + + CHECK(foo::static_bar == 42); + } + + SECTION("non-const") + { + foo::static_bar = 0; + j.apply_cb(&foo::static_set_bar, get_value); + + CHECK(foo::static_bar == 42); + } + } + + SECTION("non-static member function pointer") + { + json j(42); + + SECTION("const") + { + foo f; + + const_(j).apply_cb(&foo::set_bar, f, get_value); + + CHECK(f.bar == 42); + } + + SECTION("non-const") + { + foo f; + + j.apply_cb(&foo::set_bar, f, get_value); + + CHECK(f.bar == 42); + } + } + } +} + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP