Class template nlohmann::optional

This commit is contained in:
Alexander Karzhenkov 2021-08-28 21:31:45 +05:00
parent f6f169e7f1
commit 57f0f502cb
6 changed files with 628 additions and 43 deletions

View File

@ -20,14 +20,6 @@
#include <utility> // pair, declval #include <utility> // pair, declval
#include <valarray> // valarray #include <valarray> // valarray
#ifdef JSON_HAS_CPP_17
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
#endif
#endif
#include <nlohmann/detail/exceptions.hpp> #include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp> #include <nlohmann/detail/meta/cpp_future.hpp>
@ -36,6 +28,7 @@
#include <nlohmann/detail/meta/type_traits.hpp> #include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/string_concat.hpp> #include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/value_t.hpp> #include <nlohmann/detail/value_t.hpp>
#include <nlohmann/optional.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@ -64,6 +57,19 @@ void from_json(const BasicJsonType& j, std::optional<T>& opt)
opt = j.template get<T>(); opt = j.template get<T>();
} }
} }
template<typename BasicJsonType, typename T>
void from_json(const BasicJsonType& j, nlohmann::optional<T>& opt)
{
if (j.is_null())
{
opt = std::nullopt;
}
else
{
opt = j.template get<T>();
}
}
#endif #endif
// overloads for basic_json template parameters // overloads for basic_json template parameters

View File

@ -23,14 +23,7 @@
#include <nlohmann/detail/meta/std_fs.hpp> #include <nlohmann/detail/meta/std_fs.hpp>
#include <nlohmann/detail/meta/type_traits.hpp> #include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/value_t.hpp> #include <nlohmann/detail/value_t.hpp>
#include <nlohmann/optional.hpp>
#ifdef JSON_HAS_CPP_17
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
#endif
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@ -271,7 +264,7 @@ struct external_constructor<value_t::object>
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
template<typename BasicJsonType, typename T, template<typename BasicJsonType, typename T,
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0> enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
void to_json(BasicJsonType& j, const std::optional<T>& opt) void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept
{ {
if (opt.has_value()) if (opt.has_value())
{ {

View File

@ -0,0 +1,173 @@
#pragma once
#include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <optional>
#include <utility>
namespace nlohmann
{
template <typename T>
class optional : public std::optional<T>
{
// *INDENT-OFF*
using base_type = std::optional<T>;
template <typename U, typename = optional>
struct has_conversion_operator : std::false_type { };
template <typename U>
struct has_conversion_operator<U,
decltype(std::declval<U>().operator optional())> : std::true_type { };
template <typename... U>
using is_base_constructible_from = std::is_constructible<base_type, U...>;
template <typename U>
using is_convertible_to_base = std::is_convertible<U, base_type>;
template <typename U>
using enable_int_if = std::enable_if_t<U::value, int>;
template <typename U>
using use_conversion_operator =
enable_int_if<
has_conversion_operator<U>
>;
template <typename U>
using use_implicit_forwarding =
enable_int_if<
std::conjunction<
std::negation<has_conversion_operator<U>>,
is_base_constructible_from<U>,
is_convertible_to_base<U>
>
>;
template <typename U>
using use_explicit_forwarding =
enable_int_if<
std::conjunction<
std::negation<has_conversion_operator<U>>,
is_base_constructible_from<U>,
std::negation<is_convertible_to_base<U>>
>
>;
template <typename... U>
using can_construct_in_place_from =
enable_int_if<
is_base_constructible_from<std::in_place_t, U...>
>;
struct noexcept_fix_t {};
public:
const base_type& base() const
{
return *this;
}
constexpr optional() noexcept(noexcept(std::optional<noexcept_fix_t>())) = default;
constexpr optional(std::nullopt_t /* unused */) noexcept
: base_type(std::nullopt)
{
}
template <typename U, use_conversion_operator<U> = 0>
constexpr optional(U&& value)
noexcept(noexcept(
base_type(std::forward<U>(value).operator optional())
)) :
base_type(std::forward<U>(value).operator optional())
{
}
template <typename U = T, use_implicit_forwarding<U> = 0>
constexpr optional(U&& value)
noexcept(noexcept(
base_type(std::forward<U>(value))
)) :
base_type(std::forward<U>(value))
{
}
template <typename U, use_explicit_forwarding<U> = 0>
explicit
constexpr optional(U&& value)
noexcept(noexcept(
base_type(std::forward<U>(value))
)) :
base_type(std::forward<U>(value))
{
}
template <typename U, typename... Args, can_construct_in_place_from<U, Args...> = 0>
explicit
constexpr optional(std::in_place_t /* unused */, U&& u, Args&&... args)
noexcept(noexcept(
base_type(std::in_place, std::forward<U>(u), std::forward<Args>(args)...)
)) :
base_type(std::in_place, std::forward<U>(u), std::forward<Args>(args)...)
{
}
template <typename U, typename... Args, can_construct_in_place_from<std::initializer_list<U>&, Args...> = 0>
explicit
constexpr optional(std::in_place_t /* unused */, std::initializer_list<U> u, Args&&... args)
noexcept(noexcept(
base_type(std::in_place, u, std::forward<Args>(args)...)
)) :
base_type(std::in_place, u, std::forward<Args>(args)...)
{
}
// *INDENT-ON*
};
template<class T, class U>
constexpr bool operator == (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() == rhs.base();
}
template<class T, class U>
constexpr bool operator != (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() != rhs.base();
}
template<class T, class U>
constexpr bool operator < (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() < rhs.base();
}
template<class T, class U>
constexpr bool operator <= (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() <= rhs.base();
}
template<class T, class U>
constexpr bool operator > (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() > rhs.base();
}
template<class T, class U>
constexpr bool operator >= (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() >= rhs.base();
}
} // namespace nlohmann
#endif // JSON_HAS_CPP_17

View File

@ -169,14 +169,6 @@
#include <utility> // pair, declval #include <utility> // pair, declval
#include <valarray> // valarray #include <valarray> // valarray
#ifdef JSON_HAS_CPP_17
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
#endif
#endif
// #include <nlohmann/detail/exceptions.hpp> // #include <nlohmann/detail/exceptions.hpp>
// __ _____ _____ _____ // __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ // __| | __| | | | JSON for Modern C++
@ -4596,6 +4588,182 @@ NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/value_t.hpp> // #include <nlohmann/detail/value_t.hpp>
// #include <nlohmann/optional.hpp>
// #include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <optional>
#include <utility>
namespace nlohmann
{
template <typename T>
class optional : public std::optional<T>
{
// *INDENT-OFF*
using base_type = std::optional<T>;
template <typename U, typename = optional>
struct has_conversion_operator : std::false_type { };
template <typename U>
struct has_conversion_operator<U,
decltype(std::declval<U>().operator optional())> : std::true_type { };
template <typename... U>
using is_base_constructible_from = std::is_constructible<base_type, U...>;
template <typename U>
using is_convertible_to_base = std::is_convertible<U, base_type>;
template <typename U>
using enable_int_if = std::enable_if_t<U::value, int>;
template <typename U>
using use_conversion_operator =
enable_int_if<
has_conversion_operator<U>
>;
template <typename U>
using use_implicit_forwarding =
enable_int_if<
std::conjunction<
std::negation<has_conversion_operator<U>>,
is_base_constructible_from<U>,
is_convertible_to_base<U>
>
>;
template <typename U>
using use_explicit_forwarding =
enable_int_if<
std::conjunction<
std::negation<has_conversion_operator<U>>,
is_base_constructible_from<U>,
std::negation<is_convertible_to_base<U>>
>
>;
template <typename... U>
using can_construct_in_place_from =
enable_int_if<
is_base_constructible_from<std::in_place_t, U...>
>;
struct noexcept_fix_t {};
public:
const base_type& base() const
{
return *this;
}
constexpr optional() noexcept(noexcept(std::optional<noexcept_fix_t>())) = default;
constexpr optional(std::nullopt_t /* unused */) noexcept
: base_type(std::nullopt)
{
}
template <typename U, use_conversion_operator<U> = 0>
constexpr optional(U&& value)
noexcept(noexcept(
base_type(std::forward<U>(value).operator optional())
)) :
base_type(std::forward<U>(value).operator optional())
{
}
template <typename U = T, use_implicit_forwarding<U> = 0>
constexpr optional(U&& value)
noexcept(noexcept(
base_type(std::forward<U>(value))
)) :
base_type(std::forward<U>(value))
{
}
template <typename U, use_explicit_forwarding<U> = 0>
explicit
constexpr optional(U&& value)
noexcept(noexcept(
base_type(std::forward<U>(value))
)) :
base_type(std::forward<U>(value))
{
}
template <typename U, typename... Args, can_construct_in_place_from<U, Args...> = 0>
explicit
constexpr optional(std::in_place_t /* unused */, U&& u, Args&&... args)
noexcept(noexcept(
base_type(std::in_place, std::forward<U>(u), std::forward<Args>(args)...)
)) :
base_type(std::in_place, std::forward<U>(u), std::forward<Args>(args)...)
{
}
template <typename U, typename... Args, can_construct_in_place_from<std::initializer_list<U>&, Args...> = 0>
explicit
constexpr optional(std::in_place_t /* unused */, std::initializer_list<U> u, Args&&... args)
noexcept(noexcept(
base_type(std::in_place, u, std::forward<Args>(args)...)
)) :
base_type(std::in_place, u, std::forward<Args>(args)...)
{
}
// *INDENT-ON*
};
template<class T, class U>
constexpr bool operator == (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() == rhs.base();
}
template<class T, class U>
constexpr bool operator != (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() != rhs.base();
}
template<class T, class U>
constexpr bool operator < (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() < rhs.base();
}
template<class T, class U>
constexpr bool operator <= (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() <= rhs.base();
}
template<class T, class U>
constexpr bool operator > (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() > rhs.base();
}
template<class T, class U>
constexpr bool operator >= (const optional<T>& lhs, const optional<U>& rhs)
{
return lhs.base() >= rhs.base();
}
} // namespace nlohmann
#endif // JSON_HAS_CPP_17
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@ -4624,6 +4792,19 @@ void from_json(const BasicJsonType& j, std::optional<T>& opt)
opt = j.template get<T>(); opt = j.template get<T>();
} }
} }
template<typename BasicJsonType, typename T>
void from_json(const BasicJsonType& j, nlohmann::optional<T>& opt)
{
if (j.is_null())
{
opt = std::nullopt;
}
else
{
opt = j.template get<T>();
}
}
#endif #endif
// overloads for basic_json template parameters // overloads for basic_json template parameters
@ -5356,14 +5537,8 @@ class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
// #include <nlohmann/detail/value_t.hpp> // #include <nlohmann/detail/value_t.hpp>
// #include <nlohmann/optional.hpp>
#ifdef JSON_HAS_CPP_17
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
#endif
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@ -5604,7 +5779,7 @@ struct external_constructor<value_t::object>
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
template<typename BasicJsonType, typename T, template<typename BasicJsonType, typename T,
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0> enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
void to_json(BasicJsonType& j, const std::optional<T>& opt) void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept
{ {
if (opt.has_value()) if (opt.has_value())
{ {

View File

@ -31,14 +31,15 @@ using nlohmann::json;
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair // NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-macros")
#ifdef JSON_HAS_CPP_17 // For testing copy-initialization of std::optional and nlohmann::optional
#if __has_include(<optional>) // (clang doesn't need the suppressing)
#include <optional> #define SUPPRESS_CONVERSION_WARNING \
#elif __has_include(<experimental/optional>) DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wconversion")
#include <experimental/optional>
#endif #define RESTORE_CONVERSION_WARNING \
#endif DOCTEST_GCC_SUPPRESS_WARNING_POP
TEST_CASE("value conversion") TEST_CASE("value conversion")
{ {
@ -1586,7 +1587,22 @@ TEST_CASE("std::optional")
std::optional<std::string> opt_null; std::optional<std::string> opt_null;
CHECK(json(opt_null) == j_null); CHECK(json(opt_null) == j_null);
CHECK(std::optional<std::string>(j_null) == std::nullopt); CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
using opt_int = std::optional<int>;
auto opt1 = []() -> opt_int { return json().get<opt_int>(); };
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") SECTION("string")
@ -1622,7 +1638,7 @@ TEST_CASE("std::optional")
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}}; std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
CHECK(json(opt_array) == j_array); CHECK(json(opt_array) == j_array);
CHECK(std::vector<std::optional<int>>(j_array) == opt_array); CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
} }
SECTION("object") SECTION("object")
@ -1631,7 +1647,76 @@ TEST_CASE("std::optional")
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
CHECK(json(opt_object) == j_object); CHECK(json(opt_object) == j_object);
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object); CHECK(j_object.get<std::map<std::string, std::optional<int>>>() == opt_object);
}
}
TEST_CASE("nlohmann::optional")
{
SECTION("null")
{
json j_null;
nlohmann::optional<std::string> opt_null;
CHECK(json(opt_null) == j_null);
CHECK(j_null.get<nlohmann::optional<std::string>>() == std::nullopt);
using opt_int = nlohmann::optional<int>;
auto opt1 = []() -> opt_int { return json().get<opt_int>(); };
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<std::string> opt_string = "string";
CHECK(json(opt_string) == j_string);
CHECK(nlohmann::optional<std::string>(j_string) == opt_string);
}
SECTION("bool")
{
json j_bool = true;
nlohmann::optional<bool> opt_bool = true;
CHECK(json(opt_bool) == j_bool);
CHECK(nlohmann::optional<bool>(j_bool) == opt_bool);
}
SECTION("number")
{
json j_number = 1;
nlohmann::optional<int> opt_int = 1;
CHECK(json(opt_int) == j_number);
CHECK(nlohmann::optional<int>(j_number) == opt_int);
}
SECTION("array")
{
json j_array = {1, 2, nullptr};
std::vector<nlohmann::optional<int>> opt_array = {{1, 2, std::nullopt}};
CHECK(json(opt_array) == j_array);
CHECK(j_array.get<std::vector<nlohmann::optional<int>>>() == opt_array);
}
SECTION("object")
{
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
std::map<std::string, nlohmann::optional<int>> opt_object{{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
CHECK(json(opt_object) == j_object);
CHECK(j_object.get<std::map<std::string, nlohmann::optional<int>>>() == opt_object);
} }
} }
#endif #endif

153
tests/src/unit-optional.cpp Normal file
View File

@ -0,0 +1,153 @@
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#ifdef JSON_HAS_CPP_17
DOCTEST_GCC_SUPPRESS_WARNING_PUSH
DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast")
#include <memory>
#include <vector>
#include <utility>
using std::nullopt;
using std::in_place;
using nlohmann::optional;
using opt_int = optional<int>;
using opt_vec = optional<std::vector<int>>;
using opt_ptr = optional<std::unique_ptr<int>>;
using std_opt_int = std::optional<int>;
using std_opt_vec = std::optional<std::vector<int>>;
using std_opt_ptr = std::optional<std::unique_ptr<int>>;
// 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<int> 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