Document GCC < 11 incomplete-type error with optional members (#3669)

With GCC 10 and older in C++11/14 mode, a free to_json() for a type
holding an optional<Dummy> (Dummy constructible from json) fails with
"invalid use of incomplete type detector<...to_json_function...>".
ADL for Dummy finds the unrelated to_json and closes an instantiation
cycle through optional's converting constructor. The same error
reproduces without the library, so it can't be fixed here.

Add a FAQ entry explaining the cause and the hidden-friend workaround,
recommend hidden friends in the arbitrary types docs, and add a
regression test that keeps the workaround compiling on GCC 7-10.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-27 17:07:13 +02:00
parent 1e101ecac1
commit 86ea63185e
3 changed files with 99 additions and 0 deletions
+53
View File
@@ -241,6 +241,51 @@ class my_allocator : public std::allocator<T>
};
};
/////////////////////////////////////////////////////////////////////
// for #3669
/////////////////////////////////////////////////////////////////////
// mimics boost::optional's converting constructor, whose SFINAE check asks
// whether T is constructible from const U&
template<class T, class Arg>
struct issue3669_is_constructible
{
template<class T2, class A2, class = decltype(T2(std::declval<A2>()))>
static char test(int);
template<class, class>
static long test(...);
static constexpr bool value = sizeof(test<T, Arg>(0)) == 1;
};
template<class T>
class issue3669_optional
{
public:
issue3669_optional() = default;
template<class U>
issue3669_optional(const issue3669_optional<U>& /*unused*/, // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
typename std::enable_if<issue3669_is_constructible<T, const U&>::value, bool>::type /*unused*/ = true) {}
};
class Issue3669Dummy
{
public:
explicit Issue3669Dummy(const json& /*unused*/) {}
};
class Issue3669Holder
{
issue3669_optional<Issue3669Dummy> d{};
// GCC < 11 (C++11/14) rejects a free to_json(json&, const Issue3669Holder&)
// here, because ADL for Issue3669Dummy finds it and closes an instantiation
// cycle; a hidden friend is only visible to ADL for Issue3669Holder
friend void to_json(json& j, const Issue3669Holder& /*unused*/)
{
j = "holder";
}
};
TEST_CASE("regression tests 2")
{
SECTION("issue #1001 - Fix memory leak during parser callback")
@@ -766,6 +811,14 @@ TEST_CASE("regression tests 2")
CHECK(j == k);
}
SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json")
{
const Issue3669Holder h{};
const Issue3669Holder h2(h); // NOLINT(performance-unnecessary-copy-initialization)
const json j = h2;
CHECK(j == "holder");
}
}
TEST_CASE("regression test - parser callback must not lose a duplicate key's prior value")