Fix iterators to meet (more) std::ranges requirements
Fixes #3130. Related discussion: #3408
This commit is contained in:
parent
529e4100c5
commit
fe2c4d7777
@ -51,9 +51,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
// make sure BasicJsonType is basic_json or const basic_json
|
||||
static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,
|
||||
"iter_impl only accepts (const) basic_json");
|
||||
// superficial check for the LegacyBidirectionalIterator named requirement
|
||||
static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value
|
||||
&& std::is_base_of<std::bidirectional_iterator_tag, typename array_t::iterator::iterator_category>::value,
|
||||
"basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement.");
|
||||
|
||||
public:
|
||||
|
||||
/// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.
|
||||
/// The C++ Standard has never required user-defined iterators to derive from std::iterator.
|
||||
/// A user-defined iterator should provide publicly accessible typedefs named
|
||||
|
||||
@ -6,6 +6,10 @@
|
||||
#include <tuple> // tuple_size, get, tuple_element
|
||||
#include <utility> // move
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges> // enable_borrowed_range
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
@ -25,14 +29,14 @@ template<typename IteratorType> class iteration_proxy_value
|
||||
public:
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = iteration_proxy_value;
|
||||
using pointer = value_type * ;
|
||||
using reference = value_type & ;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using string_type = typename std::remove_cv< typename std::remove_reference<decltype( std::declval<IteratorType>().key() ) >::type >::type;
|
||||
|
||||
private:
|
||||
/// the iterator
|
||||
IteratorType anchor;
|
||||
IteratorType anchor{};
|
||||
/// an index for arrays (used to create key names)
|
||||
std::size_t array_index = 0;
|
||||
/// last stringified array index
|
||||
@ -40,15 +44,30 @@ template<typename IteratorType> class iteration_proxy_value
|
||||
/// a string representation of the array index
|
||||
mutable string_type array_index_str = "0";
|
||||
/// an empty string (to return a reference for primitive values)
|
||||
const string_type empty_str{};
|
||||
string_type empty_str{};
|
||||
|
||||
public:
|
||||
explicit iteration_proxy_value(IteratorType it) noexcept
|
||||
explicit iteration_proxy_value() = default;
|
||||
explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0)
|
||||
noexcept(std::is_nothrow_move_constructible<IteratorType>::value
|
||||
&& std::is_nothrow_default_constructible<string_type>::value)
|
||||
: anchor(std::move(it))
|
||||
, array_index(array_index_)
|
||||
{}
|
||||
|
||||
iteration_proxy_value(iteration_proxy_value const&) = default;
|
||||
iteration_proxy_value& operator=(iteration_proxy_value const&) = default;
|
||||
// older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions
|
||||
iteration_proxy_value(iteration_proxy_value&&)
|
||||
noexcept(std::is_nothrow_move_constructible<IteratorType>::value
|
||||
&& std::is_nothrow_move_constructible<string_type>::value) = default;
|
||||
iteration_proxy_value& operator=(iteration_proxy_value&&)
|
||||
noexcept(std::is_nothrow_move_assignable<IteratorType>::value
|
||||
&& std::is_nothrow_move_assignable<string_type>::value) = default;
|
||||
~iteration_proxy_value() = default;
|
||||
|
||||
/// dereference operator (needed for range-based for)
|
||||
iteration_proxy_value& operator*()
|
||||
const iteration_proxy_value& operator*() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
@ -62,6 +81,14 @@ template<typename IteratorType> class iteration_proxy_value
|
||||
return *this;
|
||||
}
|
||||
|
||||
iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp)
|
||||
{
|
||||
auto tmp = iteration_proxy_value(anchor, array_index);
|
||||
++anchor;
|
||||
++array_index;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// equality operator (needed for InputIterator)
|
||||
bool operator==(const iteration_proxy_value& o) const
|
||||
{
|
||||
@ -122,25 +149,34 @@ template<typename IteratorType> class iteration_proxy
|
||||
{
|
||||
private:
|
||||
/// the container to iterate
|
||||
typename IteratorType::reference container;
|
||||
typename IteratorType::pointer container = nullptr;
|
||||
|
||||
public:
|
||||
explicit iteration_proxy() = default;
|
||||
|
||||
/// construct iteration proxy from a container
|
||||
explicit iteration_proxy(typename IteratorType::reference cont) noexcept
|
||||
: container(cont) {}
|
||||
: container(&cont) {}
|
||||
|
||||
iteration_proxy(iteration_proxy const&) = default;
|
||||
iteration_proxy& operator=(iteration_proxy const&) = default;
|
||||
iteration_proxy(iteration_proxy&&) noexcept = default;
|
||||
iteration_proxy& operator=(iteration_proxy&&) noexcept = default;
|
||||
~iteration_proxy() = default;
|
||||
|
||||
/// return iterator begin (needed for range-based for)
|
||||
iteration_proxy_value<IteratorType> begin() noexcept
|
||||
iteration_proxy_value<IteratorType> begin() const noexcept
|
||||
{
|
||||
return iteration_proxy_value<IteratorType>(container.begin());
|
||||
return iteration_proxy_value<IteratorType>(container->begin());
|
||||
}
|
||||
|
||||
/// return iterator end (needed for range-based for)
|
||||
iteration_proxy_value<IteratorType> end() noexcept
|
||||
iteration_proxy_value<IteratorType> end() const noexcept
|
||||
{
|
||||
return iteration_proxy_value<IteratorType>(container.end());
|
||||
return iteration_proxy_value<IteratorType>(container->end());
|
||||
}
|
||||
};
|
||||
|
||||
// Structured Bindings Support
|
||||
// For further reference see https://blog.tartanllama.xyz/structured-bindings/
|
||||
// And see https://github.com/nlohmann/json/pull/1391
|
||||
@ -187,3 +223,8 @@ class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
} // namespace std
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
template <typename IteratorType>
|
||||
inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy<IteratorType>> = true;
|
||||
#endif
|
||||
|
||||
@ -118,13 +118,11 @@
|
||||
#define JSON_HAS_RANGES 0
|
||||
#elif defined(__cpp_lib_ranges)
|
||||
#define JSON_HAS_RANGES 1
|
||||
#else
|
||||
#define JSON_HAS_RANGES 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_RANGES
|
||||
#define JSON_HAS_RANGES 0
|
||||
#endif
|
||||
|
||||
#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address)
|
||||
#define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]]
|
||||
#else
|
||||
|
||||
@ -2345,13 +2345,11 @@ using is_detected_convertible =
|
||||
#define JSON_HAS_RANGES 0
|
||||
#elif defined(__cpp_lib_ranges)
|
||||
#define JSON_HAS_RANGES 1
|
||||
#else
|
||||
#define JSON_HAS_RANGES 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_RANGES
|
||||
#define JSON_HAS_RANGES 0
|
||||
#endif
|
||||
|
||||
#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address)
|
||||
#define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]]
|
||||
#else
|
||||
@ -4660,6 +4658,10 @@ constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::va
|
||||
#include <tuple> // tuple_size, get, tuple_element
|
||||
#include <utility> // move
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges> // enable_borrowed_range
|
||||
#endif
|
||||
|
||||
// #include <nlohmann/detail/meta/type_traits.hpp>
|
||||
|
||||
// #include <nlohmann/detail/value_t.hpp>
|
||||
@ -4681,14 +4683,14 @@ template<typename IteratorType> class iteration_proxy_value
|
||||
public:
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = iteration_proxy_value;
|
||||
using pointer = value_type * ;
|
||||
using reference = value_type & ;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using string_type = typename std::remove_cv< typename std::remove_reference<decltype( std::declval<IteratorType>().key() ) >::type >::type;
|
||||
|
||||
private:
|
||||
/// the iterator
|
||||
IteratorType anchor;
|
||||
IteratorType anchor{};
|
||||
/// an index for arrays (used to create key names)
|
||||
std::size_t array_index = 0;
|
||||
/// last stringified array index
|
||||
@ -4696,15 +4698,30 @@ template<typename IteratorType> class iteration_proxy_value
|
||||
/// a string representation of the array index
|
||||
mutable string_type array_index_str = "0";
|
||||
/// an empty string (to return a reference for primitive values)
|
||||
const string_type empty_str{};
|
||||
string_type empty_str{};
|
||||
|
||||
public:
|
||||
explicit iteration_proxy_value(IteratorType it) noexcept
|
||||
explicit iteration_proxy_value() = default;
|
||||
explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0)
|
||||
noexcept(std::is_nothrow_move_constructible<IteratorType>::value
|
||||
&& std::is_nothrow_default_constructible<string_type>::value)
|
||||
: anchor(std::move(it))
|
||||
, array_index(array_index_)
|
||||
{}
|
||||
|
||||
iteration_proxy_value(iteration_proxy_value const&) = default;
|
||||
iteration_proxy_value& operator=(iteration_proxy_value const&) = default;
|
||||
// older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions
|
||||
iteration_proxy_value(iteration_proxy_value&&)
|
||||
noexcept(std::is_nothrow_move_constructible<IteratorType>::value
|
||||
&& std::is_nothrow_move_constructible<string_type>::value) = default;
|
||||
iteration_proxy_value& operator=(iteration_proxy_value&&)
|
||||
noexcept(std::is_nothrow_move_assignable<IteratorType>::value
|
||||
&& std::is_nothrow_move_assignable<string_type>::value) = default;
|
||||
~iteration_proxy_value() = default;
|
||||
|
||||
/// dereference operator (needed for range-based for)
|
||||
iteration_proxy_value& operator*()
|
||||
const iteration_proxy_value& operator*() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
@ -4718,6 +4735,14 @@ template<typename IteratorType> class iteration_proxy_value
|
||||
return *this;
|
||||
}
|
||||
|
||||
iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp)
|
||||
{
|
||||
auto tmp = iteration_proxy_value(anchor, array_index);
|
||||
++anchor;
|
||||
++array_index;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// equality operator (needed for InputIterator)
|
||||
bool operator==(const iteration_proxy_value& o) const
|
||||
{
|
||||
@ -4778,25 +4803,34 @@ template<typename IteratorType> class iteration_proxy
|
||||
{
|
||||
private:
|
||||
/// the container to iterate
|
||||
typename IteratorType::reference container;
|
||||
typename IteratorType::pointer container = nullptr;
|
||||
|
||||
public:
|
||||
explicit iteration_proxy() = default;
|
||||
|
||||
/// construct iteration proxy from a container
|
||||
explicit iteration_proxy(typename IteratorType::reference cont) noexcept
|
||||
: container(cont) {}
|
||||
: container(&cont) {}
|
||||
|
||||
iteration_proxy(iteration_proxy const&) = default;
|
||||
iteration_proxy& operator=(iteration_proxy const&) = default;
|
||||
iteration_proxy(iteration_proxy&&) noexcept = default;
|
||||
iteration_proxy& operator=(iteration_proxy&&) noexcept = default;
|
||||
~iteration_proxy() = default;
|
||||
|
||||
/// return iterator begin (needed for range-based for)
|
||||
iteration_proxy_value<IteratorType> begin() noexcept
|
||||
iteration_proxy_value<IteratorType> begin() const noexcept
|
||||
{
|
||||
return iteration_proxy_value<IteratorType>(container.begin());
|
||||
return iteration_proxy_value<IteratorType>(container->begin());
|
||||
}
|
||||
|
||||
/// return iterator end (needed for range-based for)
|
||||
iteration_proxy_value<IteratorType> end() noexcept
|
||||
iteration_proxy_value<IteratorType> end() const noexcept
|
||||
{
|
||||
return iteration_proxy_value<IteratorType>(container.end());
|
||||
return iteration_proxy_value<IteratorType>(container->end());
|
||||
}
|
||||
};
|
||||
|
||||
// Structured Bindings Support
|
||||
// For further reference see https://blog.tartanllama.xyz/structured-bindings/
|
||||
// And see https://github.com/nlohmann/json/pull/1391
|
||||
@ -4844,6 +4878,11 @@ class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
|
||||
#endif
|
||||
} // namespace std
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
template <typename IteratorType>
|
||||
inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy<IteratorType>> = true;
|
||||
#endif
|
||||
|
||||
// #include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
|
||||
// #include <nlohmann/detail/meta/type_traits.hpp>
|
||||
@ -12197,9 +12236,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
// make sure BasicJsonType is basic_json or const basic_json
|
||||
static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,
|
||||
"iter_impl only accepts (const) basic_json");
|
||||
// superficial check for the LegacyBidirectionalIterator named requirement
|
||||
static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value
|
||||
&& std::is_base_of<std::bidirectional_iterator_tag, typename array_t::iterator::iterator_category>::value,
|
||||
"basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement.");
|
||||
|
||||
public:
|
||||
|
||||
/// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.
|
||||
/// The C++ Standard has never required user-defined iterators to derive from std::iterator.
|
||||
/// A user-defined iterator should provide publicly accessible typedefs named
|
||||
|
||||
@ -80,7 +80,7 @@ TEST_CASE("iterator_wrapper")
|
||||
json j = { {"A", 1}, {"B", 2} };
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : json::iterator_wrapper(j))
|
||||
for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
switch (counter++)
|
||||
{
|
||||
@ -226,7 +226,7 @@ TEST_CASE("iterator_wrapper")
|
||||
const json j = { {"A", 1}, {"B", 2} };
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : json::iterator_wrapper(j))
|
||||
for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
switch (counter++)
|
||||
{
|
||||
@ -361,7 +361,7 @@ TEST_CASE("iterator_wrapper")
|
||||
json j = { "A", "B" };
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : json::iterator_wrapper(j))
|
||||
for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
switch (counter++)
|
||||
{
|
||||
@ -507,7 +507,7 @@ TEST_CASE("iterator_wrapper")
|
||||
const json j = { "A", "B" };
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : json::iterator_wrapper(j))
|
||||
for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
switch (counter++)
|
||||
{
|
||||
@ -624,7 +624,7 @@ TEST_CASE("iterator_wrapper")
|
||||
json j = 1;
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : json::iterator_wrapper(j))
|
||||
for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
++counter;
|
||||
CHECK(i.key() == "");
|
||||
@ -693,7 +693,7 @@ TEST_CASE("iterator_wrapper")
|
||||
const json j = 1;
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : json::iterator_wrapper(j))
|
||||
for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
++counter;
|
||||
CHECK(i.key() == "");
|
||||
@ -777,7 +777,7 @@ TEST_CASE("items()")
|
||||
json j = { {"A", 1}, {"B", 2} };
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : j.items())
|
||||
for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
switch (counter++)
|
||||
{
|
||||
@ -939,7 +939,7 @@ TEST_CASE("items()")
|
||||
const json j = { {"A", 1}, {"B", 2} };
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : j.items())
|
||||
for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
switch (counter++)
|
||||
{
|
||||
@ -1074,7 +1074,7 @@ TEST_CASE("items()")
|
||||
json j = { "A", "B" };
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : j.items())
|
||||
for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
switch (counter++)
|
||||
{
|
||||
@ -1220,7 +1220,7 @@ TEST_CASE("items()")
|
||||
const json j = { "A", "B" };
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : j.items())
|
||||
for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
switch (counter++)
|
||||
{
|
||||
@ -1337,7 +1337,7 @@ TEST_CASE("items()")
|
||||
json j = 1;
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : j.items())
|
||||
for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
++counter;
|
||||
CHECK(i.key() == "");
|
||||
@ -1406,7 +1406,7 @@ TEST_CASE("items()")
|
||||
const json j = 1;
|
||||
int counter = 1;
|
||||
|
||||
for (auto& i : j.items())
|
||||
for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
|
||||
{
|
||||
++counter;
|
||||
CHECK(i.key() == "");
|
||||
|
||||
@ -32,6 +32,12 @@ SOFTWARE.
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
// JSON_HAS_CPP_20 (magic keyword; do not remove)
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
TEST_CASE("iterators 2")
|
||||
{
|
||||
SECTION("iterator comparisons")
|
||||
@ -881,4 +887,100 @@ TEST_CASE("iterators 2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
SECTION("ranges")
|
||||
{
|
||||
SECTION("concepts")
|
||||
{
|
||||
using nlohmann::detail::iteration_proxy_value;
|
||||
CHECK(std::bidirectional_iterator<json::iterator>);
|
||||
CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
|
||||
|
||||
CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
|
||||
CHECK(std::ranges::bidirectional_range<json>);
|
||||
|
||||
using nlohmann::detail::iteration_proxy;
|
||||
using items_type = decltype(std::declval<json&>().items());
|
||||
CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
|
||||
CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
|
||||
CHECK(std::ranges::input_range<items_type>);
|
||||
}
|
||||
|
||||
// libstdc++ algorithms don't work with Clang 15 (04/2022)
|
||||
#if !defined(__clang__) || (defined(__clang__) && defined(__GLIBCXX__))
|
||||
SECTION("algorithms")
|
||||
{
|
||||
SECTION("copy")
|
||||
{
|
||||
json j{"foo", "bar"};
|
||||
auto j_copied = json::array();
|
||||
|
||||
std::ranges::copy(j, std::back_inserter(j_copied));
|
||||
|
||||
CHECK(j == j_copied);
|
||||
}
|
||||
|
||||
SECTION("find_if")
|
||||
{
|
||||
json j{1, 3, 2, 4};
|
||||
auto j_even = json::array();
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
auto it = std::ranges::find_if(j, [](int v) noexcept
|
||||
{
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#else
|
||||
auto it = std::ranges::find_if(j, [](const json & j) noexcept
|
||||
{
|
||||
int v;
|
||||
j.get_to(v);
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#endif
|
||||
|
||||
CHECK(*it == 2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// libstdc++ views don't work with Clang 15 (04/2022)
|
||||
// libc++ hides limited ranges implementation behind guard macro
|
||||
#if !(defined(__clang__) && (defined(__GLIBCXX__) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)))
|
||||
SECTION("views")
|
||||
{
|
||||
SECTION("reverse")
|
||||
{
|
||||
json j{1, 2, 3, 4, 5};
|
||||
json j_expected{5, 4, 3, 2, 1};
|
||||
|
||||
auto reversed = j | std::views::reverse;
|
||||
CHECK(reversed == j_expected);
|
||||
}
|
||||
|
||||
SECTION("transform")
|
||||
{
|
||||
json j
|
||||
{
|
||||
{ "a_key", "a_value"},
|
||||
{ "b_key", "b_value"},
|
||||
{ "c_key", "c_value"},
|
||||
};
|
||||
json j_expected{"a_key", "b_key", "c_key"};
|
||||
|
||||
auto transformed = j.items() | std::views::transform([](const auto & item)
|
||||
{
|
||||
return item.key();
|
||||
});
|
||||
auto j_transformed = json::array();
|
||||
std::ranges::copy(transformed, std::back_inserter(j_transformed));
|
||||
|
||||
CHECK(j_transformed == j_expected);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user