nlohmann / json

JSON for Modern C++
https://json.nlohmann.me
MIT License
41.25k stars 6.57k forks source link

iteration_proxy has limited usefulness in C++20 range views #4371

Open captaincrutches opened 1 month ago

captaincrutches commented 1 month ago

Description

This is a sort-of continuation, sort-of reopening of #3130 - it's not exactly the same use case, but certain applications of items() on a json object still don't play nicely with C++20 std::ranges.

In particular, while items() can be used as an input for views like std::views::transform, that result isn't usable in situations that require a forward_iterator, such as constructing a container from the output range's iterators. See the code below for what I mean.

The compilation error suggests the view iterator's iterator_category is not defined, which is the case if the base range doesn't model forward_range.

I did some debugging using static_asserts, and found that while iteration_proxy_value does meet all the requirements for a forward_iterator, its iterator_category is explicitly exposed as only std::input_iterator_tag. Simply changing that to std::forward_iterator_tag (or removing it and letting iterator_traits deduce it) in my testing makes it fully model forward_iterator, so that items() models forward_range and the view is usable in this case.

Reproduction steps

Expected vs. actual results

iteration_proxy is just a wrapper around the actual iterator of json itself, which does satisfy forward_iterator, so I expect iteration_proxy_value to also satisfy forward_iterator. Instead, it only satisfies input_iterator.

Minimal code example

#include <nlohmann/json.hpp>

#include <ranges>
#include <string>
#include <vector>

int main()
{
    // This works
    nlohmann::json arr { 1, 2, 3 };
    auto arrTransform = std::views::transform([](auto&& element){ return element.template get<int>() * 2; });
    auto arrView = arr | arrTransform;
    std::vector<int> arrVec{arrView.begin(), arrView.end()};

    // This doesn't work
    nlohmann::json obj {
        { "one", 1 },
        { "two", 2 },
        { "three", 3 }
    };
    auto objItems = obj.items();
    auto objTransform = std::views::transform([](auto&& element){ return element.key(); });
    auto objView = objItems | objTransform;
    std::vector<std::string> keys{objView.begin(), objView.end()};  // Fails to compile
}

Error messages

json-range.cpp: In function ‘int main()’:
json-range.cpp:24:65: error: no matching function for call to ‘std::vector<std::__cxx11::basic_string<char>, std::allocator<std::__cxx11::basic_string<char> > >::vector(<brace-enclosed initializer list>)’
   23 |     std::vector<std::string> keys{objView.begin(), objView.end()};  // Fails to compile
      |                                                                 ^
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/vector:66,
                 from /usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/functional:64,
                 from /usr/include/nlohmann/json.hpp:23,
                 from json-range.cpp:1:
/usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/bits/stl_vector.h:707:9: note: candidate: ‘template<class _InputIterator, class> constexpr std::vector<_Tp, _Alloc>::vector(_InputIterator, _InputIterator, const allocator_type&) [with <template-parameter-2-2> = _InputIterator; _Tp = std::__cxx11::basic_string<char>; _Alloc = std::allocator<std::__cxx11::basic_string<char> >]’
  707 |         vector(_InputIterator __first, _InputIterator __last,
      |         ^~~~~~
/usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/bits/stl_vector.h:707:9: note:   template argument deduction/substitution failed:
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/bits/stl_algobase.h:65,
                 from /usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/algorithm:60,
                 from /usr/include/nlohmann/json.hpp:21:
/usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/bits/stl_iterator_base_types.h: In substitution of ‘template<class _InIter> using std::_RequireInputIter = std::__enable_if_t<std::is_convertible<typename std::iterator_traits< <template-parameter-1-1> >::iterator_category, std::input_iterator_tag>::value> [with _InIter = std::ranges::transform_view<std::ranges::ref_view<nlohmann::json_abi_v3_11_3::detail::iteration_proxy<nlohmann::json_abi_v3_11_3::detail::iter_impl<nlohmann::json_abi_v3_11_3::basic_json<> > > >, main()::<lambda(auto:24&&)> >::_Iterator<false>]’:
/usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/bits/stl_vector.h:705:9:   required from here
/usr/lib/gcc/x86_64-pc-linux-gnu/13/include/g++-v13/bits/stl_iterator_base_types.h:250:11: error: no type named ‘iterator_category’ in ‘struct std::iterator_traits<std::ranges::transform_view<std::ranges::ref_view<nlohmann::json_abi_v3_11_3::detail::iteration_proxy<nlohmann::json_abi_v3_11_3::detail::iter_impl<nlohmann::json_abi_v3_11_3::basic_json<> > > >, main()::<lambda(auto:24&&)> >::_Iterator<false> >’
  250 |     using _RequireInputIter =
      |           ^~~~~~~~~~~~~~~~~

Compiler and operating system

Gentoo, GCC 13.2

Library version

3.11.3

Validation