Constructing array from C++20 ranges view does not work
Description
Constructing a json object from a C++20 view (e.g. std::ranges::transform_view/std::ranges::filter_view) does not work.
Reproduction steps
Constructing a json object from a std::vector works fine, but constructing from a more basic range such as a vector passed through std::views::filter does not work. This holds for a range of json object as well as a range of plain ints.
Expected vs. actual results
Expected: json value constructs as array with elements from the view (e.g. below: with contents [37, 42, 21]).
Actual: Fails to compile as is_compatible_array_type returns false for some reason.
Minimal code example
#include <iostream>
#include <ranges>
#include <vector>
#include <nlohmann/json.hpp>
int main() {
std::vector<int> nums{1, 2, 37, 42, 21};
auto filteredNums = nums | std::views::filter([](int i) { return i > 10; });
nlohmann::json j(filteredNums);
std::cout << j << std::endl;
}
Error messages
<source>:10:20: error: no matching constructor for initialization of 'nlohmann::json' (aka 'basic_json<>')
10 | nlohmann::json j(filteredNums);
| ^ ~~~~~~~~~~~~
[...]
/opt/compiler-explorer/libs/nlohmann_json/trunk/single_include/nlohmann/json.hpp:20942:5: note: candidate template ignored: requirement 'nlohmann::detail::is_compatible_type<nlohmann::basic_json<std::map, std::vector, std::basic_string<char, std::char_traits<char>, std::allocator<char>>, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char>>, void>, std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int>>>, (lambda at <source>:9:51)>>::value' was not satisfied [with CompatibleType = typename __invoke_result<const std::ranges::views::_Filter &, std::vector<int, std::allocator<int>> &, const (lambda at <source>:9:51) &>::type &, U = detail::uncvref_t<typename __invoke_result<const std::ranges::views::_Filter &, std::vector<int, std::allocator<int>> &, const (lambda at <source>:9:51) &>::type &>]
20942 | basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
| ^
[...]
Compiler and operating system
Clang 22 / GCC 16
Library version
3.11.3, 3.12.0
Validation
- [ ] The bug also occurs if the latest version from the
developbranch is used. - [ ] I can successfully compile and run the unit tests.
@stevenwdv You didn't check the "The bug also occurs if the latest version from the develop branch is used." box.
Does that mean it doesn't happen anymore, or did you just not try it?
@gregmarr I didn't try, although I don't know what version Compiler Explorer has. Do you want me to try?
The 3.11.3 version is almost two years old, should at least confirm in 3.12.0 which is from April.
@gregmarr Ah, sorry, apparently the latest version was kept back in our repo by a transitive dependency specifying an older version. Unfortunately, the same error still occurs in 3.12.0:
/home/swdv/Downloads/tst/test.cpp:10:20: error: no matching constructor for initialization of 'nlohmann::json'
(aka 'basic_json<>')
10 | nlohmann::json j(filteredNums);
[...]
/home/swdv/.conan2/p/nlohmd014ef7748f4b/p/include/nlohmann/json.hpp:838:5: note: candidate template ignored:
requirement 'nlohmann::detail::is_compatible_type<nlohmann::basic_json<std::map, std::vector, std::basic_string<char,
std::char_traits<char>, std::allocator<char>>, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer,
std::vector<unsigned char, std::allocator<unsigned char>>, void>,
std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int>>>, (lambda at
/home/swdv/Downloads/tst/test.cpp:9:51)>>::value' was not satisfied [with CompatibleType =
filter_view<views::all_t<std::vector<int, std::allocator<int>> &>, (lambda at /home/swdv/Downloads/tst/test.cpp:9:51)> &, U =
detail::uncvref_t<filter_view<views::all_t<std::vector<int, std::allocator<int>> &>, (lambda at
/home/swdv/Downloads/tst/test.cpp:9:51)> &>]
838 | basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
| ^
[...]
As far as I can tell, the current version of nlohmann/json does not yet provide a constructor overload that accepts C++20 range views (like std::ranges::filter_view or std::ranges::transform_view).
A practical workaround is to convert the view to a container first. For example, in C++23:
auto filtered = nums | std::views::filter([](int i) { return i > 10; }) | std::ranges::tostd::vector();
nlohmann::json j(filtered);
@CrysisKiller I'm not sure, because it really seems like they intended to support a broader set of ranges if you look at is_compatible_array_type, but somewhere some check fails. But yeah, first creating a vector would be a workaround.
@stevenwdv I tried checking nlohmann::detail::is_compatible_array_type for the type of filteredNums to see whether it’s considered a valid argument for the JSON constructor. It evaluates to false, which explains why the constructor is not selected.
Example:
std::vector
using ViewType = decltype(filteredNums); constexpr bool isSupported = nlohmann::detail::is_compatible_array_type<nlohmann::json, ViewType>::value;
std::cout << std::boolalpha << "is_compatible_array_type for filter_view: " << isSupported << "\n";
I found some workaround by specializing is_compatible_array_type and wrapping the filter_view into ref_view and then it worked. the solution is not the best but I checked the other traits and I see the nlohmann::detail::is_range<R>::is_iterator_begin evaluated to false for views and I checked the nlohmann::detail::is_iterator_traits, tries to detect value_type_t, difference_type_t, pointer_t, iterator_category_t, reference_t typedefs and std::views doesn't have some or all of them (not sure about it).