diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index f7c972d5a3..c7a37ad65a 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -20,14 +20,6 @@ #include // pair, declval #include // valarray -#ifdef JSON_HAS_CPP_17 - #if __has_include() - #include - #elif __has_include() - #include - #endif -#endif - #include #include #include @@ -36,6 +28,7 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -64,6 +57,19 @@ void from_json(const BasicJsonType& j, std::optional& opt) opt = j.template get(); } } + +template +void from_json(const BasicJsonType& j, nlohmann::optional& opt) +{ + if (j.is_null()) + { + opt = std::nullopt; + } + else + { + opt = j.template get(); + } +} #endif // overloads for basic_json template parameters diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index c14a1eb5eb..3e66c8ac20 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -23,14 +23,7 @@ #include #include #include - -#ifdef JSON_HAS_CPP_17 - #if __has_include() - #include - #elif __has_include() - #include - #endif -#endif +#include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -271,7 +264,7 @@ struct external_constructor #ifdef JSON_HAS_CPP_17 template::value, int> = 0> -void to_json(BasicJsonType& j, const std::optional& opt) +void to_json(BasicJsonType& j, const std::optional& opt) noexcept { if (opt.has_value()) { diff --git a/include/nlohmann/optional.hpp b/include/nlohmann/optional.hpp new file mode 100644 index 0000000000..29b844c7e2 --- /dev/null +++ b/include/nlohmann/optional.hpp @@ -0,0 +1,173 @@ +#pragma once + +#include + +#ifdef JSON_HAS_CPP_17 + +#include +#include + +namespace nlohmann +{ + +template +class optional : public std::optional +{ + // *INDENT-OFF* + + using base_type = std::optional; + + template + struct has_conversion_operator : std::false_type { }; + + template + struct has_conversion_operator().operator optional())> : std::true_type { }; + + template + using is_base_constructible_from = std::is_constructible; + + template + using is_convertible_to_base = std::is_convertible; + + template + using enable_int_if = std::enable_if_t; + + template + using use_conversion_operator = + enable_int_if< + has_conversion_operator + >; + + template + using use_implicit_forwarding = + enable_int_if< + std::conjunction< + std::negation>, + is_base_constructible_from, + is_convertible_to_base + > + >; + + template + using use_explicit_forwarding = + enable_int_if< + std::conjunction< + std::negation>, + is_base_constructible_from, + std::negation> + > + >; + + template + using can_construct_in_place_from = + enable_int_if< + is_base_constructible_from + >; + + struct noexcept_fix_t {}; + + public: + + const base_type& base() const + { + return *this; + } + + constexpr optional() noexcept(noexcept(std::optional())) = default; + + constexpr optional(std::nullopt_t /* unused */) noexcept + : base_type(std::nullopt) + { + } + + template = 0> + constexpr optional(U&& value) + noexcept(noexcept( + base_type(std::forward(value).operator optional()) + )) : + base_type(std::forward(value).operator optional()) + { + } + + template = 0> + constexpr optional(U&& value) + noexcept(noexcept( + base_type(std::forward(value)) + )) : + base_type(std::forward(value)) + { + } + + template = 0> + explicit + constexpr optional(U&& value) + noexcept(noexcept( + base_type(std::forward(value)) + )) : + base_type(std::forward(value)) + { + } + + template = 0> + explicit + constexpr optional(std::in_place_t /* unused */, U&& u, Args&&... args) + noexcept(noexcept( + base_type(std::in_place, std::forward(u), std::forward(args)...) + )) : + base_type(std::in_place, std::forward(u), std::forward(args)...) + { + } + + template &, Args...> = 0> + explicit + constexpr optional(std::in_place_t /* unused */, std::initializer_list u, Args&&... args) + noexcept(noexcept( + base_type(std::in_place, u, std::forward(args)...) + )) : + base_type(std::in_place, u, std::forward(args)...) + { + } + + // *INDENT-ON* +}; + +template +constexpr bool operator == (const optional& lhs, const optional& rhs) +{ + return lhs.base() == rhs.base(); +} + +template +constexpr bool operator != (const optional& lhs, const optional& rhs) +{ + return lhs.base() != rhs.base(); +} + +template +constexpr bool operator < (const optional& lhs, const optional& rhs) +{ + return lhs.base() < rhs.base(); +} + +template +constexpr bool operator <= (const optional& lhs, const optional& rhs) +{ + return lhs.base() <= rhs.base(); +} + +template +constexpr bool operator > (const optional& lhs, const optional& rhs) +{ + return lhs.base() > rhs.base(); +} + +template +constexpr bool operator >= (const optional& lhs, const optional& rhs) +{ + return lhs.base() >= rhs.base(); +} + +} // namespace nlohmann + +#endif // JSON_HAS_CPP_17 diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4dfaa2bcc0..dbd5e1a6f1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -169,14 +169,6 @@ #include // pair, declval #include // valarray -#ifdef JSON_HAS_CPP_17 - #if __has_include() - #include - #elif __has_include() - #include - #endif -#endif - // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -4596,6 +4588,182 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + + +// #include + + +#ifdef JSON_HAS_CPP_17 + +#include +#include + +namespace nlohmann +{ + +template +class optional : public std::optional +{ + // *INDENT-OFF* + + using base_type = std::optional; + + template + struct has_conversion_operator : std::false_type { }; + + template + struct has_conversion_operator().operator optional())> : std::true_type { }; + + template + using is_base_constructible_from = std::is_constructible; + + template + using is_convertible_to_base = std::is_convertible; + + template + using enable_int_if = std::enable_if_t; + + template + using use_conversion_operator = + enable_int_if< + has_conversion_operator + >; + + template + using use_implicit_forwarding = + enable_int_if< + std::conjunction< + std::negation>, + is_base_constructible_from, + is_convertible_to_base + > + >; + + template + using use_explicit_forwarding = + enable_int_if< + std::conjunction< + std::negation>, + is_base_constructible_from, + std::negation> + > + >; + + template + using can_construct_in_place_from = + enable_int_if< + is_base_constructible_from + >; + + struct noexcept_fix_t {}; + + public: + + const base_type& base() const + { + return *this; + } + + constexpr optional() noexcept(noexcept(std::optional())) = default; + + constexpr optional(std::nullopt_t /* unused */) noexcept + : base_type(std::nullopt) + { + } + + template = 0> + constexpr optional(U&& value) + noexcept(noexcept( + base_type(std::forward(value).operator optional()) + )) : + base_type(std::forward(value).operator optional()) + { + } + + template = 0> + constexpr optional(U&& value) + noexcept(noexcept( + base_type(std::forward(value)) + )) : + base_type(std::forward(value)) + { + } + + template = 0> + explicit + constexpr optional(U&& value) + noexcept(noexcept( + base_type(std::forward(value)) + )) : + base_type(std::forward(value)) + { + } + + template = 0> + explicit + constexpr optional(std::in_place_t /* unused */, U&& u, Args&&... args) + noexcept(noexcept( + base_type(std::in_place, std::forward(u), std::forward(args)...) + )) : + base_type(std::in_place, std::forward(u), std::forward(args)...) + { + } + + template &, Args...> = 0> + explicit + constexpr optional(std::in_place_t /* unused */, std::initializer_list u, Args&&... args) + noexcept(noexcept( + base_type(std::in_place, u, std::forward(args)...) + )) : + base_type(std::in_place, u, std::forward(args)...) + { + } + + // *INDENT-ON* +}; + +template +constexpr bool operator == (const optional& lhs, const optional& rhs) +{ + return lhs.base() == rhs.base(); +} + +template +constexpr bool operator != (const optional& lhs, const optional& rhs) +{ + return lhs.base() != rhs.base(); +} + +template +constexpr bool operator < (const optional& lhs, const optional& rhs) +{ + return lhs.base() < rhs.base(); +} + +template +constexpr bool operator <= (const optional& lhs, const optional& rhs) +{ + return lhs.base() <= rhs.base(); +} + +template +constexpr bool operator > (const optional& lhs, const optional& rhs) +{ + return lhs.base() > rhs.base(); +} + +template +constexpr bool operator >= (const optional& lhs, const optional& rhs) +{ + return lhs.base() >= rhs.base(); +} + +} // namespace nlohmann + +#endif // JSON_HAS_CPP_17 + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -4624,6 +4792,19 @@ void from_json(const BasicJsonType& j, std::optional& opt) opt = j.template get(); } } + +template +void from_json(const BasicJsonType& j, nlohmann::optional& opt) +{ + if (j.is_null()) + { + opt = std::nullopt; + } + else + { + opt = j.template get(); + } +} #endif // overloads for basic_json template parameters @@ -5356,14 +5537,8 @@ class tuple_element> // #include +// #include -#ifdef JSON_HAS_CPP_17 - #if __has_include() - #include - #elif __has_include() - #include - #endif -#endif NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -5604,7 +5779,7 @@ struct external_constructor #ifdef JSON_HAS_CPP_17 template::value, int> = 0> -void to_json(BasicJsonType& j, const std::optional& opt) +void to_json(BasicJsonType& j, const std::optional& opt) noexcept { if (opt.has_value()) { diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index 49b6ccc418..72ef03de21 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -31,14 +31,15 @@ using nlohmann::json; // NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-macros") -#ifdef JSON_HAS_CPP_17 - #if __has_include() - #include - #elif __has_include() - #include - #endif -#endif +// For testing copy-initialization of std::optional and nlohmann::optional +// (clang doesn't need the suppressing) +#define SUPPRESS_CONVERSION_WARNING \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wconversion") + +#define RESTORE_CONVERSION_WARNING \ + DOCTEST_GCC_SUPPRESS_WARNING_POP TEST_CASE("value conversion") { @@ -1586,7 +1587,22 @@ TEST_CASE("std::optional") std::optional opt_null; CHECK(json(opt_null) == j_null); - CHECK(std::optional(j_null) == std::nullopt); + CHECK(j_null.get>() == std::nullopt); + + using opt_int = std::optional; + + auto opt1 = []() -> opt_int { return json().get(); }; + auto opt2 = []() -> opt_int { return opt_int(json()); }; // NOLINT(modernize-return-braced-init-list) + + CHECK(opt1() == std::nullopt); + CHECK_THROWS_AS(opt2(), json::type_error&); + +#if JSON_USE_IMPLICIT_CONVERSIONS + SUPPRESS_CONVERSION_WARNING + auto opt3 = []() -> opt_int { return json(); }; + RESTORE_CONVERSION_WARNING + CHECK_THROWS_AS(opt3(), json::type_error&); +#endif } SECTION("string") @@ -1622,7 +1638,7 @@ TEST_CASE("std::optional") std::vector> opt_array = {{1, 2, std::nullopt}}; CHECK(json(opt_array) == j_array); - CHECK(std::vector>(j_array) == opt_array); + CHECK(j_array.get>>() == opt_array); } SECTION("object") @@ -1631,7 +1647,76 @@ TEST_CASE("std::optional") std::map> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; CHECK(json(opt_object) == j_object); - CHECK(std::map>(j_object) == opt_object); + CHECK(j_object.get>>() == opt_object); + } +} + +TEST_CASE("nlohmann::optional") +{ + SECTION("null") + { + json j_null; + nlohmann::optional opt_null; + + CHECK(json(opt_null) == j_null); + CHECK(j_null.get>() == std::nullopt); + + using opt_int = nlohmann::optional; + + auto opt1 = []() -> opt_int { return json().get(); }; + auto opt2 = []() -> opt_int { return opt_int(json()); }; // NOLINT(modernize-return-braced-init-list) + SUPPRESS_CONVERSION_WARNING + auto opt3 = []() -> opt_int { return json(); }; + RESTORE_CONVERSION_WARNING + + CHECK(opt1() == std::nullopt); + CHECK(opt2() == std::nullopt); + CHECK(opt3() == std::nullopt); + } + + SECTION("string") + { + json j_string = "string"; + nlohmann::optional opt_string = "string"; + + CHECK(json(opt_string) == j_string); + CHECK(nlohmann::optional(j_string) == opt_string); + } + + SECTION("bool") + { + json j_bool = true; + nlohmann::optional opt_bool = true; + + CHECK(json(opt_bool) == j_bool); + CHECK(nlohmann::optional(j_bool) == opt_bool); + } + + SECTION("number") + { + json j_number = 1; + nlohmann::optional opt_int = 1; + + CHECK(json(opt_int) == j_number); + CHECK(nlohmann::optional(j_number) == opt_int); + } + + SECTION("array") + { + json j_array = {1, 2, nullptr}; + std::vector> opt_array = {{1, 2, std::nullopt}}; + + CHECK(json(opt_array) == j_array); + CHECK(j_array.get>>() == opt_array); + } + + SECTION("object") + { + json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}}; + std::map> opt_object{{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; + + CHECK(json(opt_object) == j_object); + CHECK(j_object.get>>() == opt_object); } } #endif diff --git a/tests/src/unit-optional.cpp b/tests/src/unit-optional.cpp new file mode 100644 index 0000000000..53a7f87986 --- /dev/null +++ b/tests/src/unit-optional.cpp @@ -0,0 +1,153 @@ +#include "doctest_compatibility.h" + +#include + +#ifdef JSON_HAS_CPP_17 + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") + +#include +#include +#include + +using std::nullopt; +using std::in_place; +using nlohmann::optional; + +using opt_int = optional; +using opt_vec = optional>; +using opt_ptr = optional>; + +using std_opt_int = std::optional; +using std_opt_vec = std::optional>; +using std_opt_ptr = std::optional>; + +// NOLINTBEGIN(bugprone-unchecked-optional-access,bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + +TEST_CASE("nlohmann::optional comparison") +{ + CHECK(opt_int() == nullopt); + CHECK(nullopt == opt_int()); + CHECK(opt_int() == opt_int(nullopt)); + CHECK(opt_int(0) == opt_int(0)); + CHECK_FALSE(opt_int(0) == nullopt); + CHECK_FALSE(nullopt == opt_int(0)); + CHECK_FALSE(opt_int(0) == opt_int(1)); + + CHECK(opt_int(0) != nullopt); + CHECK(nullopt != opt_int(0)); + CHECK(opt_int(0) != opt_int(1)); + CHECK_FALSE(opt_int() != nullopt); + CHECK_FALSE(nullopt != opt_int()); + CHECK_FALSE(opt_int() != opt_int(nullopt)); + CHECK_FALSE(opt_int(0) != opt_int(0)); + + CHECK(opt_int(0) > nullopt); + CHECK(opt_int(1) > opt_int(0)); + CHECK_FALSE(nullopt > opt_int(0)); + CHECK_FALSE(opt_int(0) > opt_int(1)); + + CHECK(opt_int(0) >= nullopt); + CHECK(opt_int(1) >= opt_int(0)); + CHECK_FALSE(nullopt >= opt_int(0)); + CHECK_FALSE(opt_int(0) >= opt_int(1)); + + CHECK(nullopt < opt_int(0)); + CHECK(opt_int(0) < opt_int(1)); + CHECK_FALSE(opt_int(0) < nullopt); + CHECK_FALSE(opt_int(1) < opt_int(0)); + + CHECK(nullopt <= opt_int(0)); + CHECK(opt_int(0) <= opt_int(1)); + CHECK_FALSE(opt_int(0) <= nullopt); + CHECK_FALSE(opt_int(1) <= opt_int(0)); +} + +TEST_CASE("nlohmann::optional constructors") +{ + struct S1 + { + operator int() noexcept + { + return 0; + } + }; + struct S2 : S1 + { + operator opt_int() noexcept + { + return nullopt; + } + }; + + CHECK(opt_int(S1()) == opt_int(0)); + CHECK(opt_int(S2()) == nullopt); + + CHECK(opt_int(S1()) == std_opt_int(S1())); + CHECK(opt_int(S2()) != std_opt_int(S2())); + + CHECK(opt_int(std_opt_int(0)) == opt_int(0)); + CHECK(std_opt_int(opt_int(0)) == opt_int(0)); + + CHECK(opt_int(in_place) == std_opt_int(in_place)); + CHECK(opt_vec(in_place) == std_opt_vec(in_place)); + CHECK(opt_ptr(in_place) == std_opt_ptr(in_place)); + + CHECK(opt_vec(in_place, 5)->size() == 5); + CHECK(opt_vec(in_place, {1, 2, 3}) == std_opt_vec(in_place, {1, 2, 3})); + CHECK(**opt_ptr(in_place, new int{42}) == **std_opt_ptr(in_place, new int{42})); + + std::vector vec{1, 2, 3}; + CHECK(*opt_vec(in_place, vec.begin(), vec.end()) == vec); + + CHECK(opt_vec({1, 2, 3})->size() == 3); +} + +TEST_CASE("nlohmann::optional copy") +{ + opt_int opt1 = 111; + std_opt_int opt2 = 222; + + SECTION("1") + { + opt1 = std::as_const(opt2); + CHECK(*opt1 == 222); + CHECK(*opt_int(std::as_const(opt1)) == 222); + } + + SECTION("2") + { + opt2 = std::as_const(opt1); + CHECK(*opt2 == 111); + CHECK(*opt_int(std::as_const(opt2)) == 111); + } +} + +TEST_CASE("nlohmann::optional move") +{ + opt_ptr opt1(new int(111)); + std_opt_ptr opt2(new int(222)); + + SECTION("1") + { + opt1 = std::move(opt2); + CHECK(*opt2 == nullptr); + CHECK(**opt1 == 222); + CHECK(**opt_ptr(std::move(opt1)) == 222); + } + + SECTION("2") + { + opt2 = std::move(opt1); + CHECK(*opt1 == nullptr); + CHECK(**opt2 == 111); + CHECK(**opt_ptr(std::move(opt2)) == 111); + } +} + +// NOLINTEND(bugprone-unchecked-optional-access,bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // JSON_HAS_CPP_17