From dcee778c1e72c9b08bea2b7b099d7e5c78393409 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= <theo.delrieu@tanker.io>
Date: Wed, 17 Jan 2018 12:17:38 +0100
Subject: [PATCH] fix sfinae on basic_json UDT constructor

Avoid compiler errors when performing SFINAE checks on basic_json
and incomplete types.
---
 develop/detail/meta.hpp | 26 ++++++++++++++++++++++++-
 develop/json.hpp        | 16 +++++++---------
 src/json.hpp            | 42 +++++++++++++++++++++++++++++++----------
 test/src/unit-udt.cpp   | 19 +++++++++++++++++++
 4 files changed, 83 insertions(+), 20 deletions(-)

diff --git a/develop/detail/meta.hpp b/develop/detail/meta.hpp
index bea42a07..585b8d44 100644
--- a/develop/detail/meta.hpp
+++ b/develop/detail/meta.hpp
@@ -96,6 +96,14 @@ template<> struct priority_tag<0> {};
 // has_/is_ functions //
 ////////////////////////
 
+// source: https://stackoverflow.com/a/37193089/4116453
+
+template <typename T, typename = void>
+struct is_complete_type : std::false_type {};
+
+template <typename T>
+struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
+
 NLOHMANN_JSON_HAS_HELPER(mapped_type);
 NLOHMANN_JSON_HAS_HELPER(key_type);
 NLOHMANN_JSON_HAS_HELPER(value_type);
@@ -171,7 +179,6 @@ struct is_compatible_integer_type
         RealIntegerType, CompatibleNumberIntegerType > ::value;
 };
 
-
 // trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists
 template<typename BasicJsonType, typename T>
 struct has_from_json
@@ -221,6 +228,23 @@ struct has_to_json
                                       std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
 };
 
+template <typename BasicJsonType, typename CompatibleCompleteType>
+struct is_compatible_complete_type
+{
+    static constexpr bool value =
+        not std::is_base_of<std::istream, CompatibleCompleteType>::value and
+        not std::is_same<BasicJsonType, CompatibleCompleteType>::value and
+        not is_basic_json_nested_type<BasicJsonType, CompatibleCompleteType>::value and
+        has_to_json<BasicJsonType, CompatibleCompleteType>::value;
+};
+
+template <typename BasicJsonType, typename CompatibleType>
+struct is_compatible_type
+    : conjunction<is_complete_type<CompatibleType>,
+      is_compatible_complete_type<BasicJsonType, CompatibleType>>
+{
+};
+
 // taken from ranges-v3
 template<typename T>
 struct static_const
diff --git a/develop/json.hpp b/develop/json.hpp
index f4c139e4..26a2b111 100644
--- a/develop/json.hpp
+++ b/develop/json.hpp
@@ -1551,15 +1551,13 @@ class basic_json
 
     @since version 2.1.0
     */
-    template<typename CompatibleType, typename U = detail::uncvref_t<CompatibleType>,
-             detail::enable_if_t<not std::is_base_of<std::istream, U>::value and
-                                 not std::is_same<U, basic_json_t>::value and
-                                 not detail::is_basic_json_nested_type<
-                                     basic_json_t, U>::value and
-                                 detail::has_to_json<basic_json, U>::value,
-                                 int> = 0>
-    basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer<U>::to_json(
-                std::declval<basic_json_t&>(), std::forward<CompatibleType>(val))))
+    template <typename CompatibleType,
+              typename U = detail::uncvref_t<CompatibleType>,
+              detail::enable_if_t<
+                  detail::is_compatible_type<basic_json_t, U>::value, int> = 0>
+    basic_json(CompatibleType && val) noexcept(noexcept(
+                JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
+                                           std::forward<CompatibleType>(val))))
     {
         JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
         assert_invariant();
diff --git a/src/json.hpp b/src/json.hpp
index a80cc36a..d95ae8db 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -312,6 +312,14 @@ template<> struct priority_tag<0> {};
 // has_/is_ functions //
 ////////////////////////
 
+// source: https://stackoverflow.com/a/37193089/4116453
+
+template <typename T, typename = void>
+struct is_complete_type : std::false_type {};
+
+template <typename T>
+struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
+
 NLOHMANN_JSON_HAS_HELPER(mapped_type);
 NLOHMANN_JSON_HAS_HELPER(key_type);
 NLOHMANN_JSON_HAS_HELPER(value_type);
@@ -387,7 +395,6 @@ struct is_compatible_integer_type
         RealIntegerType, CompatibleNumberIntegerType > ::value;
 };
 
-
 // trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists
 template<typename BasicJsonType, typename T>
 struct has_from_json
@@ -437,6 +444,23 @@ struct has_to_json
                                       std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
 };
 
+template <typename BasicJsonType, typename CompatibleCompleteType>
+struct is_compatible_complete_type
+{
+    static constexpr bool value =
+        not std::is_base_of<std::istream, CompatibleCompleteType>::value and
+        not std::is_same<BasicJsonType, CompatibleCompleteType>::value and
+        not is_basic_json_nested_type<BasicJsonType, CompatibleCompleteType>::value and
+        has_to_json<BasicJsonType, CompatibleCompleteType>::value;
+};
+
+template <typename BasicJsonType, typename CompatibleType>
+struct is_compatible_type
+    : conjunction<is_complete_type<CompatibleType>,
+      is_compatible_complete_type<BasicJsonType, CompatibleType>>
+{
+};
+
 // taken from ranges-v3
 template<typename T>
 struct static_const
@@ -8839,15 +8863,13 @@ class basic_json
 
     @since version 2.1.0
     */
-    template<typename CompatibleType, typename U = detail::uncvref_t<CompatibleType>,
-             detail::enable_if_t<not std::is_base_of<std::istream, U>::value and
-                                 not std::is_same<U, basic_json_t>::value and
-                                 not detail::is_basic_json_nested_type<
-                                     basic_json_t, U>::value and
-                                 detail::has_to_json<basic_json, U>::value,
-                                 int> = 0>
-    basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer<U>::to_json(
-                std::declval<basic_json_t&>(), std::forward<CompatibleType>(val))))
+    template <typename CompatibleType,
+              typename U = detail::uncvref_t<CompatibleType>,
+              detail::enable_if_t<
+                  detail::is_compatible_type<basic_json_t, U>::value, int> = 0>
+    basic_json(CompatibleType && val) noexcept(noexcept(
+                JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
+                                           std::forward<CompatibleType>(val))))
     {
         JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
         assert_invariant();
diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp
index 4fd6f7ec..a4adc3be 100644
--- a/test/src/unit-udt.cpp
+++ b/test/src/unit-udt.cpp
@@ -692,3 +692,22 @@ TEST_CASE("custom serializer that does adl by default", "[udt]")
     CHECK(me == j.get<udt::person>());
     CHECK(me == cj.get<udt::person>());
 }
+
+namespace
+{
+struct incomplete;
+
+// std::is_constructible is broken on macOS' libc++
+// use the cppreference implementation
+
+template <typename T, typename = void>
+struct is_constructible_patched : std::false_type {};
+
+template <typename T>
+struct is_constructible_patched<T, decltype(void(json(std::declval<T>())))> : std::true_type {};
+}
+
+TEST_CASE("an incomplete type does not trigger a compiler error in non-evaluated context", "[udt]")
+{
+    static_assert(not is_constructible_patched<json, incomplete>::value, "");
+}