mirror of
https://github.com/nlohmann/json.git
synced 2026-04-05 07:38:52 +00:00
meta: fix is_compatible/constructible traits (#3020)
The previous version relied on the existence of an 'iterator' type. As mentioned in comments, this is not the proper way to do it and causes issues with certain types (e.g. views from range-v3). Add a 'is_range' trait that properly detects the return type of 'begin'/'end', and use it in instead.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility> // pair
|
||||
#include <utility> // declval, pair
|
||||
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||
#include <nlohmann/detail/meta/detected.hpp>
|
||||
|
||||
// This file contains all internal macro definitions
|
||||
// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them
|
||||
@@ -292,6 +293,45 @@
|
||||
inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
|
||||
inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
|
||||
|
||||
|
||||
// inspired from https://stackoverflow.com/a/26745591
|
||||
// allows to call any std function as if (e.g. with begin):
|
||||
// using std::begin; begin(x);
|
||||
//
|
||||
// it allows using the detected idiom to retrieve the return type
|
||||
// of such an expression
|
||||
#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \
|
||||
namespace detail { \
|
||||
using std::std_name; \
|
||||
\
|
||||
template<typename... T> \
|
||||
using result_of_##std_name = decltype(std_name(std::declval<T>()...)); \
|
||||
} \
|
||||
\
|
||||
namespace detail2 { \
|
||||
struct std_name##_tag \
|
||||
{ \
|
||||
}; \
|
||||
\
|
||||
template<typename... T> \
|
||||
std_name##_tag std_name(T&&...); \
|
||||
\
|
||||
template<typename... T> \
|
||||
using result_of_##std_name = decltype(std_name(std::declval<T>()...)); \
|
||||
\
|
||||
template<typename... T> \
|
||||
struct would_call_std_##std_name \
|
||||
{ \
|
||||
static constexpr auto const value = ::nlohmann::detail:: \
|
||||
is_detected_exact<std_name##_tag, result_of_##std_name, T...>::value; \
|
||||
}; \
|
||||
} /* namespace detail2 */ \
|
||||
\
|
||||
template<typename... T> \
|
||||
struct would_call_std_##std_name : detail2::would_call_std_##std_name<T...> \
|
||||
{ \
|
||||
}
|
||||
|
||||
#ifndef JSON_USE_IMPLICIT_CONVERSIONS
|
||||
#define JSON_USE_IMPLICIT_CONVERSIONS 1
|
||||
#endif
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||
#undef NLOHMANN_BASIC_JSON_TPL
|
||||
#undef JSON_EXPLICIT
|
||||
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
||||
|
||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||
|
||||
8
include/nlohmann/detail/meta/call_std/begin.hpp
Normal file
8
include/nlohmann/detail/meta/call_std/begin.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin);
|
||||
} // namespace nlohmann
|
||||
8
include/nlohmann/detail/meta/call_std/end.hpp
Normal file
8
include/nlohmann/detail/meta/call_std/end.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end);
|
||||
} // namespace nlohmann
|
||||
@@ -5,8 +5,11 @@
|
||||
#include <utility> // declval
|
||||
#include <tuple> // tuple
|
||||
|
||||
#include <nlohmann/detail/iterators/iterator_traits.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
#include <nlohmann/detail/iterators/iterator_traits.hpp>
|
||||
#include <nlohmann/detail/meta/call_std/begin.hpp>
|
||||
#include <nlohmann/detail/meta/call_std/end.hpp>
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/meta/detected.hpp>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
@@ -79,9 +82,6 @@ using reference_t = typename T::reference;
|
||||
template<typename T>
|
||||
using iterator_category_t = typename T::iterator_category;
|
||||
|
||||
template<typename T>
|
||||
using iterator_t = typename T::iterator;
|
||||
|
||||
template<typename T, typename... Args>
|
||||
using to_json_function = decltype(T::to_json(std::declval<Args>()...));
|
||||
|
||||
@@ -217,6 +217,31 @@ struct is_iterator_traits<iterator_traits<T>>
|
||||
is_detected<reference_t, traits>::value;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct is_range
|
||||
{
|
||||
private:
|
||||
using t_ref = typename std::add_lvalue_reference<T>::type;
|
||||
|
||||
using iterator = detected_t<result_of_begin, t_ref>;
|
||||
using sentinel = detected_t<result_of_end, t_ref>;
|
||||
|
||||
// to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator
|
||||
// and https://en.cppreference.com/w/cpp/iterator/sentinel_for
|
||||
// but reimplementing these would be too much work, as a lot of other concepts are used underneath
|
||||
static constexpr auto is_iterator_begin =
|
||||
is_iterator_traits<iterator_traits<iterator>>::value;
|
||||
|
||||
public:
|
||||
static constexpr bool value = !std::is_same<iterator, nonesuch>::value && !std::is_same<sentinel, nonesuch>::value && is_iterator_begin;
|
||||
};
|
||||
|
||||
template<typename R>
|
||||
using iterator_t = enable_if_t<is_range<R>::value, result_of_begin<decltype(std::declval<R&>())>>;
|
||||
|
||||
template<typename T>
|
||||
using range_value_t = value_type_t<iterator_traits<iterator_t<T>>>;
|
||||
|
||||
// The following implementation of is_complete_type is taken from
|
||||
// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/
|
||||
// and is written by Xiang Fan who agreed to using it in this library.
|
||||
@@ -291,8 +316,9 @@ struct is_compatible_string_type_impl : std::false_type {};
|
||||
template<typename BasicJsonType, typename CompatibleStringType>
|
||||
struct is_compatible_string_type_impl <
|
||||
BasicJsonType, CompatibleStringType,
|
||||
enable_if_t<is_detected_exact<typename BasicJsonType::string_t::value_type,
|
||||
value_type_t, CompatibleStringType>::value >>
|
||||
enable_if_t<is_detected_convertible<typename BasicJsonType::string_t::value_type,
|
||||
range_value_t,
|
||||
CompatibleStringType>::value >>
|
||||
{
|
||||
static constexpr auto value =
|
||||
is_constructible<typename BasicJsonType::string_t, CompatibleStringType>::value;
|
||||
@@ -327,17 +353,13 @@ struct is_compatible_array_type_impl : std::false_type {};
|
||||
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||
struct is_compatible_array_type_impl <
|
||||
BasicJsonType, CompatibleArrayType,
|
||||
enable_if_t < is_detected<value_type_t, CompatibleArrayType>::value&&
|
||||
enable_if_t <
|
||||
is_detected<iterator_t, CompatibleArrayType>::value&&
|
||||
// This is needed because json_reverse_iterator has a ::iterator type...
|
||||
// Therefore it is detected as a CompatibleArrayType.
|
||||
// The real fix would be to have an Iterable concept.
|
||||
!is_iterator_traits <
|
||||
iterator_traits<CompatibleArrayType >>::value >>
|
||||
is_iterator_traits<iterator_traits<detected_t<iterator_t, CompatibleArrayType>>>::value >>
|
||||
{
|
||||
static constexpr bool value =
|
||||
is_constructible<BasicJsonType,
|
||||
typename CompatibleArrayType::value_type>::value;
|
||||
range_value_t<CompatibleArrayType>>::value;
|
||||
};
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||
@@ -359,28 +381,26 @@ struct is_constructible_array_type_impl <
|
||||
BasicJsonType, ConstructibleArrayType,
|
||||
enable_if_t < !std::is_same<ConstructibleArrayType,
|
||||
typename BasicJsonType::value_type>::value&&
|
||||
!is_compatible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||
is_default_constructible<ConstructibleArrayType>::value&&
|
||||
(std::is_move_assignable<ConstructibleArrayType>::value ||
|
||||
std::is_copy_assignable<ConstructibleArrayType>::value)&&
|
||||
is_detected<value_type_t, ConstructibleArrayType>::value&&
|
||||
is_detected<iterator_t, ConstructibleArrayType>::value&&
|
||||
is_iterator_traits<iterator_traits<detected_t<iterator_t, ConstructibleArrayType>>>::value&&
|
||||
is_detected<range_value_t, ConstructibleArrayType>::value&&
|
||||
is_complete_type <
|
||||
detected_t<value_type_t, ConstructibleArrayType >>::value >>
|
||||
detected_t<range_value_t, ConstructibleArrayType >>::value >>
|
||||
{
|
||||
static constexpr bool value =
|
||||
// This is needed because json_reverse_iterator has a ::iterator type,
|
||||
// furthermore, std::back_insert_iterator (and other iterators) have a
|
||||
// base class `iterator`... Therefore it is detected as a
|
||||
// ConstructibleArrayType. The real fix would be to have an Iterable
|
||||
// concept.
|
||||
!is_iterator_traits<iterator_traits<ConstructibleArrayType>>::value &&
|
||||
using value_type = range_value_t<ConstructibleArrayType>;
|
||||
|
||||
(std::is_same<typename ConstructibleArrayType::value_type,
|
||||
typename BasicJsonType::array_t::value_type>::value ||
|
||||
has_from_json<BasicJsonType,
|
||||
typename ConstructibleArrayType::value_type>::value ||
|
||||
has_non_default_from_json <
|
||||
BasicJsonType, typename ConstructibleArrayType::value_type >::value);
|
||||
static constexpr bool value =
|
||||
std::is_same<value_type,
|
||||
typename BasicJsonType::array_t::value_type>::value ||
|
||||
has_from_json<BasicJsonType,
|
||||
value_type>::value ||
|
||||
has_non_default_from_json <
|
||||
BasicJsonType,
|
||||
value_type >::value;
|
||||
};
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleArrayType>
|
||||
|
||||
Reference in New Issue
Block a user