Json: GCC 10: Compilation error when including any before including json header in C++17 mode

Created on 21 May 2020  路  10Comments  路  Source: nlohmann/json

What is the issue you have?

A compilation error just including parts of the standard library and this library in a specific order when using C++17 and GCC 10.


Please describe the steps to reproduce the issue.


Consider the following file:

#include <any>
#include <nlohmann/json>

When compiling it in C++17 mode, it won't compile with GCC 10

Here's a compile explorer link that shows the issue.

When changing <any> to <variant>, there is no issue. I don't know if any other standard header causes this bug.

When putting the #include <any> after #include <nlohmann/json>, it compiles. DEMO.

What is the expected behavior?

It should compile.

And what is the actual behavior instead?

It shows very strange errors and fail to compile.

Which compiler and operating system are you using?


  • Compiler: GCC 10
  • Operating system: Arch Linux

Which version of the library did you use?

  • [x] latest release version 3.7.3
  • [ ] other release - please state the version: ___
  • [ ] the develop branch

If you experience a compilation error: can you compile and run the unit tests?

  • [x] yes
  • [ ] no - please copy/paste the error message below

I had issues with some CMake related tests but it seem unrelated to this bug.

bug

Most helpful comment

I suppose I found the answer to that mind-boggling question. I'm not a compiler expert though so take that into consideration.

MINIMAL CODE EXAMPLE

First of all, enclosed is the minimal code of single header where I removed everything that wasn't necessary to reproduce the bug:

file _json.hpp_

#include <string>
#include <type_traits>
#include <utility>

template<typename>
class basic_json;

using json = basic_json<std::string>;

class json_ref
{
  public:
    template <typename T,
              bool = std::is_constructible<json, T>::value>
    json_ref(T &&){}
};

template<typename>
class basic_json
{
  public:
    basic_json(json_ref) {}
};

namespace std {

template<>
void swap<json>(json&, json&) noexcept {}

} // namespace std

file _main.cpp_

#include <any>
#include "json.hpp"

int main() {}

OBSERVATIONS

Then I started playing with it and I discovered strange relations and how the code could be fixed by:
1) swapping around includes of "json.hpp" and <any>
2) removing #include <any>
3) changing #include <any> to #include <variant>
4) changing using json = basic_json<std::string>; to using json = basic_json<int>;
5) removing bool = std::is_constructible<json, T>::value>
6) chaning json_ref(T &&){} to json_ref(T) / json_ref(const T&) / json_ref(const T&&)
7) moving the json_ref class to the bottom of json.hpp (but only to the bottom, doesn't work between basic_json and namespace std extension)
8) removing the external swap (i.e. extension of std)
9) removing the contructor of basic_json(json_ref) {}
and much more :D

ANALYSIS AND EXPLANATION

From std::swap specialization to std::any

GCC always starts its ranting with the external swap function (our std::swap specialization) so it's clearly the source of the error. This explicit specialization forces checking all other existing swaps to be inspected and eventually leads to the bug.

Now, as I followed a GCC stacktrace, I found that along the chain, it performs some SFINAE of form:

    struct __do_is_swappable_impl
    {
      template<typename _Tp, typename
               = decltype(swap(std::declval<_Tp&>(), std::declval<_Tp&>()))>
    // (...)

_Note: Clang also reaches this code, but I lose the track of where it goes after_

But from here it "magically" goes to std::any constructor. Why? Check out std::any external swap function signature; it is:

  /// Exchange the states of two @c any objects.
  inline void swap(any& __x, any& __y) noexcept { __x.swap(__y); }

So, the previously mentioned decltype(swap(std::declval<_Tp&>(), std::declval<_Tp&>()))> considers converting json to std::any implicitly (through one of std::any non-explicit constructors) as a better match than our std::swap specialization.

From std::any constructor to std::__is_constructible_impl

As the compiler goes further, it has to check SFINAE requirements of std::any contructor:

    /// Construct with a copy of @p __value as the contained object.
    template <typename _Tp, typename _VTp = _Decay_if_not_any<_Tp>,
          typename _Mgr = _Manager<_VTp>,
          enable_if_t<is_copy_constructible<_VTp>::value
              && !__is_in_place_type<_VTp>::value, bool> = true>
      any(_Tp&& __value)
      // (...)

It calls the metafunction of std::is_copy_constructible onto our json type which is later dispatched to the _mysterious_ std::__is_constructible_impl.

From std::__is_constructible_impl to std::__is_constructible_impl (indirect incompleteness)

What we soon get is that std::__is_constructible_impl is incomplete. Why? Because inserting json to that mysterious std::__is_constructible_impl involves inspecting all constructors of json obviously, but since we have such a constructor:

basic_json(json_ref) {}

we need to check constructors of json_ref too (as we depend on that type). Compiler just tries everything, e.g. it looks for some implicit conversions here. The catch here is that the dependent constructor

template <typename T,
              bool = std::is_constructible<json, T>::value>
    json_ref(T &&){}

instantiates std::is_constructible, but the latter derives from std::__is_constructible_impl.

I mean:
std::__is_constructible_impl depends on basic_json
basic_json depends on json_ref
json_ref depends on std::is_constructible
std::is_constructible depends on std::__is_constructible_impl

AFTERWORD

IMO it's hard to tell where and what to fix. From user POV it is bollocks that #include <any> rejects the code for seemingly no reason.

Though, I'm gonna be a devil's advocate here and I defend GCC as I reckon that GCC works fine here. IMHO, it's rather the defect of C++17 standard that requires us to write std::swap specializations (at least until C++20) and forbids us from overloading std::swap for our own types. This makes the compiler think that swap<json>(json&, json&) is worse than swap(any&, any&). As we see, this results in convoluted scenarios which take hours of analysis and return no clear error to the user.

Last but not least, unfortunately I have no clue what clang does after inspecting __do_is_swappable_impl. Actually, on my system GCC and Clang share the same header files but they split up in this point. Who knows, maybe clang is more right or wrong here.

All 10 comments

For reference, here is the error message from godbolt:

In file included from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/move.h:57,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/nested_exception.h:40,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/exception:148,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/new:41,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/any:37,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits: In instantiation of 'struct std::is_constructible<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>':
#### json.nlohmann/json.hpp:11120:79:   required by substitution of 'template<class ... Args, typename std::enable_if<std::is_constructible<nlohmann::basic_json<>, Args ...>::value, int>::type <anonymous> > nlohmann::detail::json_ref<nlohmann::basic_json<> >::json_ref(Args&& ...) [with Args = {const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&}; typename std::enable_if<std::is_constructible<nlohmann::basic_json<>, Args ...>::value, int>::type <anonymous> = <missing>]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:901:30:   required from 'struct std::__is_constructible_impl<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:930:12:   required from 'struct std::__is_copy_constructible_impl<nlohmann::basic_json<>, true>'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:936:12:   required from 'struct std::is_copy_constructible<nlohmann::basic_json<> >'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/any:185:49:   required by substitution of 'template<class _Tp, class _VTp, class _Mgr, typename std::enable_if<(std::is_copy_constructible<_Tp>::value && (! std::__is_in_place_type<_VTp>::value)), bool>::type <anonymous> > std::any::any(_Tp&&) [with _Tp = nlohmann::basic_json<>&; _VTp = nlohmann::basic_json<>; _Mgr = std::any::_Manager_external<nlohmann::basic_json<> >; typename std::enable_if<(std::is_copy_constructible<_Tp>::value && (! std::__is_in_place_type<_VTp>::value)), bool>::type <anonymous> = <missing>]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2670:31:   required by substitution of 'template<class _Tp, class> static std::true_type std::__swappable_details::__do_is_swappable_impl::__test(int) [with _Tp = nlohmann::basic_json<>; <template-parameter-1-2> = <missing>]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2694:35:   required from 'struct std::__is_swappable_impl<nlohmann::basic_json<> >'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2720:12:   required from 'struct std::is_swappable<nlohmann::basic_json<> >'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2740:26:   required from 'constexpr const bool std::is_swappable_v<nlohmann::basic_json<> >'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/optional:1201:51:   required by substitution of 'template<class _Tp> std::enable_if_t<(!(is_move_constructible_v<_Tp> && is_swappable_v<_Tp>))> std::swap(std::optional<_Tp>&, std::optional<_Tp>&) [with _Tp = nlohmann::basic_json<>]'
#### json.nlohmann/json.hpp:22677:1:   required from here
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:906:12: error: invalid use of incomplete type 'struct std::__is_constructible_impl<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>'
  906 |     struct is_constructible
      |            ^~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:900:12: note: declaration of 'struct std::__is_constructible_impl<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>'
  900 |     struct __is_constructible_impl
      |            ^~~~~~~~~~~~~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/vector:66,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/functional:62,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/pstl/glue_algorithm_defs.h:13,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/algorithm:74,
                 from #### json.nlohmann/json.hpp:37:
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/stl_uninitialized.h: In instantiation of '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const nlohmann::basic_json<>*, std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > > >; _ForwardIterator = nlohmann::basic_json<>*]':
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/stl_uninitialized.h:325:37:   required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const nlohmann::basic_json<>*, std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > > >; _ForwardIterator = nlohmann::basic_json<>*; _Tp = nlohmann::basic_json<>]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/stl_vector.h:558:31:   required from 'std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = nlohmann::basic_json<>; _Alloc = std::allocator<nlohmann::basic_json<> >]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/ext/new_allocator.h:150:4:   required from 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > >; _Args = {const std::vector<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>, std::allocator<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer> > >&}; _Tp = std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > >]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/alloc_traits.h:512:17:   required from 'static void std::allocator_traits<std::allocator<_Tp1> >::construct(std::allocator_traits<std::allocator<_Tp1> >::allocator_type&, _Up*, _Args&& ...) [with _Up = std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > >; _Args = {const std::vector<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>, std::allocator<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer> > >&}; _Tp = std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > >; std::allocator_traits<std::allocator<_Tp1> >::allocator_type = std::allocator<std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > > >]'
#### json.nlohmann/json.hpp:15381:35:   [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
#### json.nlohmann/json.hpp:16365:25:   required from 'nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::basic_json(const nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>&) [with ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::adl_serializer]'
#### json.nlohmann/json.hpp:4737:31:   required from 'bool nlohmann::detail::json_sax_dom_callback_parser<BasicJsonType>::end_object() [with BasicJsonType = nlohmann::basic_json<>]'
#### json.nlohmann/json.hpp:8847:33:   required from 'bool nlohmann::detail::parser<BasicJsonType>::sax_parse_internal(SAX*) [with SAX = nlohmann::detail::json_sax_dom_callback_parser<nlohmann::basic_json<> >; BasicJsonType = nlohmann::basic_json<>]'
#### json.nlohmann/json.hpp:8738:31:   required from 'void nlohmann::detail::parser<BasicJsonType>::parse(bool, BasicJsonType&) [with BasicJsonType = nlohmann::basic_json<>]'
#### json.nlohmann/json.hpp:20882:79:   required from 'static nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer> nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::parse(IteratorType, IteratorType, nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::parser_callback_t, bool) [with IteratorType = const char*; typename std::enable_if<std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<_InputIterator>::iterator_category>::value, int>::type <anonymous> = 0; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::adl_serializer; nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::parser_callback_t = std::function<bool(int, nlohmann::detail::parser<nlohmann::basic_json<> >::parse_event_t, nlohmann::basic_json<>&)>]'
#### json.nlohmann/json.hpp:22700:42:   required from here
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/stl_uninitialized.h:137:72: error: 'value' is not a member of 'std::is_constructible<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>'
  137 |       static_assert(is_constructible<_ValueType2, decltype(*__first)>::value,
      |                                                                        ^~~~~
ASM generation compiler returned: 1
In file included from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/move.h:57,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/nested_exception.h:40,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/exception:148,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/new:41,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/any:37,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits: In instantiation of 'struct std::is_constructible<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>':
#### json.nlohmann/json.hpp:11120:79:   required by substitution of 'template<class ... Args, typename std::enable_if<std::is_constructible<nlohmann::basic_json<>, Args ...>::value, int>::type <anonymous> > nlohmann::detail::json_ref<nlohmann::basic_json<> >::json_ref(Args&& ...) [with Args = {const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&}; typename std::enable_if<std::is_constructible<nlohmann::basic_json<>, Args ...>::value, int>::type <anonymous> = <missing>]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:901:30:   required from 'struct std::__is_constructible_impl<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:930:12:   required from 'struct std::__is_copy_constructible_impl<nlohmann::basic_json<>, true>'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:936:12:   required from 'struct std::is_copy_constructible<nlohmann::basic_json<> >'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/any:185:49:   required by substitution of 'template<class _Tp, class _VTp, class _Mgr, typename std::enable_if<(std::is_copy_constructible<_Tp>::value && (! std::__is_in_place_type<_VTp>::value)), bool>::type <anonymous> > std::any::any(_Tp&&) [with _Tp = nlohmann::basic_json<>&; _VTp = nlohmann::basic_json<>; _Mgr = std::any::_Manager_external<nlohmann::basic_json<> >; typename std::enable_if<(std::is_copy_constructible<_Tp>::value && (! std::__is_in_place_type<_VTp>::value)), bool>::type <anonymous> = <missing>]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2670:31:   required by substitution of 'template<class _Tp, class> static std::true_type std::__swappable_details::__do_is_swappable_impl::__test(int) [with _Tp = nlohmann::basic_json<>; <template-parameter-1-2> = <missing>]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2694:35:   required from 'struct std::__is_swappable_impl<nlohmann::basic_json<> >'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2720:12:   required from 'struct std::is_swappable<nlohmann::basic_json<> >'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2740:26:   required from 'constexpr const bool std::is_swappable_v<nlohmann::basic_json<> >'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/optional:1201:51:   required by substitution of 'template<class _Tp> std::enable_if_t<(!(is_move_constructible_v<_Tp> && is_swappable_v<_Tp>))> std::swap(std::optional<_Tp>&, std::optional<_Tp>&) [with _Tp = nlohmann::basic_json<>]'
#### json.nlohmann/json.hpp:22677:1:   required from here
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:906:12: error: invalid use of incomplete type 'struct std::__is_constructible_impl<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>'
  906 |     struct is_constructible
      |            ^~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:900:12: note: declaration of 'struct std::__is_constructible_impl<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>'
  900 |     struct __is_constructible_impl
      |            ^~~~~~~~~~~~~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/vector:66,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/functional:62,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/pstl/glue_algorithm_defs.h:13,
                 from /opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/algorithm:74,
                 from #### json.nlohmann/json.hpp:37:
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/stl_uninitialized.h: In instantiation of '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const nlohmann::basic_json<>*, std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > > >; _ForwardIterator = nlohmann::basic_json<>*]':
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/stl_uninitialized.h:325:37:   required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const nlohmann::basic_json<>*, std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > > >; _ForwardIterator = nlohmann::basic_json<>*; _Tp = nlohmann::basic_json<>]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/stl_vector.h:558:31:   required from 'std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = nlohmann::basic_json<>; _Alloc = std::allocator<nlohmann::basic_json<> >]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/ext/new_allocator.h:150:4:   required from 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > >; _Args = {const std::vector<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>, std::allocator<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer> > >&}; _Tp = std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > >]'
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/alloc_traits.h:512:17:   required from 'static void std::allocator_traits<std::allocator<_Tp1> >::construct(std::allocator_traits<std::allocator<_Tp1> >::allocator_type&, _Up*, _Args&& ...) [with _Up = std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > >; _Args = {const std::vector<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>, std::allocator<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer> > >&}; _Tp = std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > >; std::allocator_traits<std::allocator<_Tp1> >::allocator_type = std::allocator<std::vector<nlohmann::basic_json<>, std::allocator<nlohmann::basic_json<> > > >]'
#### json.nlohmann/json.hpp:15381:35:   [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
#### json.nlohmann/json.hpp:16365:25:   required from 'nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::basic_json(const nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>&) [with ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::adl_serializer]'
#### json.nlohmann/json.hpp:4737:31:   required from 'bool nlohmann::detail::json_sax_dom_callback_parser<BasicJsonType>::end_object() [with BasicJsonType = nlohmann::basic_json<>]'
#### json.nlohmann/json.hpp:8847:33:   required from 'bool nlohmann::detail::parser<BasicJsonType>::sax_parse_internal(SAX*) [with SAX = nlohmann::detail::json_sax_dom_callback_parser<nlohmann::basic_json<> >; BasicJsonType = nlohmann::basic_json<>]'
#### json.nlohmann/json.hpp:8738:31:   required from 'void nlohmann::detail::parser<BasicJsonType>::parse(bool, BasicJsonType&) [with BasicJsonType = nlohmann::basic_json<>]'
#### json.nlohmann/json.hpp:20882:79:   required from 'static nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer> nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::parse(IteratorType, IteratorType, nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::parser_callback_t, bool) [with IteratorType = const char*; typename std::enable_if<std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<_InputIterator>::iterator_category>::value, int>::type <anonymous> = 0; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::adl_serializer; nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::parser_callback_t = std::function<bool(int, nlohmann::detail::parser<nlohmann::basic_json<> >::parse_event_t, nlohmann::basic_json<>&)>]'
#### json.nlohmann/json.hpp:22700:42:   required from here
/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/bits/stl_uninitialized.h:137:72: error: 'value' is not a member of 'std::is_constructible<nlohmann::basic_json<>, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long int, long unsigned int, double, std::allocator, nlohmann::adl_serializer>&>'
  137 |       static_assert(is_constructible<_ValueType2, decltype(*__first)>::value,
      |                                                                        ^~~~~
Execution build compiler returned: 1

It seem like it doesn't happen with the develop branch. Here's a compiler explorer like that shows this.

I'm quite unsure what caused this. I'm glad this is fixed now. I would be ready to close this issue unless someone would like to take the time to explain what happened there. I tried to find an explanation but didn't found any.

I neither have a clue nor could reproduce it with the develop branch either...

Was this maybe fixed by #2034 ? I experienced that issue also without including , most standard library headers caused this, but it was fixed by #2034.

EDIT: godbolt to prove my point: https://godbolt.org/z/4Pswt6

Left is the version without #2034 merged, right is after the merge.

As I have no clue and this is a nice explanation, I am happy to close this issue. Thanks for digging into this!

I suppose I found the answer to that mind-boggling question. I'm not a compiler expert though so take that into consideration.

MINIMAL CODE EXAMPLE

First of all, enclosed is the minimal code of single header where I removed everything that wasn't necessary to reproduce the bug:

file _json.hpp_

#include <string>
#include <type_traits>
#include <utility>

template<typename>
class basic_json;

using json = basic_json<std::string>;

class json_ref
{
  public:
    template <typename T,
              bool = std::is_constructible<json, T>::value>
    json_ref(T &&){}
};

template<typename>
class basic_json
{
  public:
    basic_json(json_ref) {}
};

namespace std {

template<>
void swap<json>(json&, json&) noexcept {}

} // namespace std

file _main.cpp_

#include <any>
#include "json.hpp"

int main() {}

OBSERVATIONS

Then I started playing with it and I discovered strange relations and how the code could be fixed by:
1) swapping around includes of "json.hpp" and <any>
2) removing #include <any>
3) changing #include <any> to #include <variant>
4) changing using json = basic_json<std::string>; to using json = basic_json<int>;
5) removing bool = std::is_constructible<json, T>::value>
6) chaning json_ref(T &&){} to json_ref(T) / json_ref(const T&) / json_ref(const T&&)
7) moving the json_ref class to the bottom of json.hpp (but only to the bottom, doesn't work between basic_json and namespace std extension)
8) removing the external swap (i.e. extension of std)
9) removing the contructor of basic_json(json_ref) {}
and much more :D

ANALYSIS AND EXPLANATION

From std::swap specialization to std::any

GCC always starts its ranting with the external swap function (our std::swap specialization) so it's clearly the source of the error. This explicit specialization forces checking all other existing swaps to be inspected and eventually leads to the bug.

Now, as I followed a GCC stacktrace, I found that along the chain, it performs some SFINAE of form:

    struct __do_is_swappable_impl
    {
      template<typename _Tp, typename
               = decltype(swap(std::declval<_Tp&>(), std::declval<_Tp&>()))>
    // (...)

_Note: Clang also reaches this code, but I lose the track of where it goes after_

But from here it "magically" goes to std::any constructor. Why? Check out std::any external swap function signature; it is:

  /// Exchange the states of two @c any objects.
  inline void swap(any& __x, any& __y) noexcept { __x.swap(__y); }

So, the previously mentioned decltype(swap(std::declval<_Tp&>(), std::declval<_Tp&>()))> considers converting json to std::any implicitly (through one of std::any non-explicit constructors) as a better match than our std::swap specialization.

From std::any constructor to std::__is_constructible_impl

As the compiler goes further, it has to check SFINAE requirements of std::any contructor:

    /// Construct with a copy of @p __value as the contained object.
    template <typename _Tp, typename _VTp = _Decay_if_not_any<_Tp>,
          typename _Mgr = _Manager<_VTp>,
          enable_if_t<is_copy_constructible<_VTp>::value
              && !__is_in_place_type<_VTp>::value, bool> = true>
      any(_Tp&& __value)
      // (...)

It calls the metafunction of std::is_copy_constructible onto our json type which is later dispatched to the _mysterious_ std::__is_constructible_impl.

From std::__is_constructible_impl to std::__is_constructible_impl (indirect incompleteness)

What we soon get is that std::__is_constructible_impl is incomplete. Why? Because inserting json to that mysterious std::__is_constructible_impl involves inspecting all constructors of json obviously, but since we have such a constructor:

basic_json(json_ref) {}

we need to check constructors of json_ref too (as we depend on that type). Compiler just tries everything, e.g. it looks for some implicit conversions here. The catch here is that the dependent constructor

template <typename T,
              bool = std::is_constructible<json, T>::value>
    json_ref(T &&){}

instantiates std::is_constructible, but the latter derives from std::__is_constructible_impl.

I mean:
std::__is_constructible_impl depends on basic_json
basic_json depends on json_ref
json_ref depends on std::is_constructible
std::is_constructible depends on std::__is_constructible_impl

AFTERWORD

IMO it's hard to tell where and what to fix. From user POV it is bollocks that #include <any> rejects the code for seemingly no reason.

Though, I'm gonna be a devil's advocate here and I defend GCC as I reckon that GCC works fine here. IMHO, it's rather the defect of C++17 standard that requires us to write std::swap specializations (at least until C++20) and forbids us from overloading std::swap for our own types. This makes the compiler think that swap<json>(json&, json&) is worse than swap(any&, any&). As we see, this results in convoluted scenarios which take hours of analysis and return no clear error to the user.

Last but not least, unfortunately I have no clue what clang does after inspecting __do_is_swappable_impl. Actually, on my system GCC and Clang share the same header files but they split up in this point. Who knows, maybe clang is more right or wrong here.

Maybe #2176 is related to this.

Thank you for this explanation. This is why I stumbled across the std::swap specialization while investigating.

:)
FYI, I've reported a regression bug in GCC bugzilla as I understand how it behaves but I don't know whether this is how it should be.

Please note the following resolution from Jonathan Wakely on the GCC issue:

The json code is incorrect and should be fixed.

The std::swap specialization is wrong and should be replaced by a normal (non-template) overload in the same namespace as the json type:

void swap(json&, json&) noexcept;

The json_ref constructor should be constrained to avoid recursive instantiations with incomplete types, e.g.

template<typename T, typename U>
  using is_not = std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, U>;

class json_ref
{
  public:
    template <typename T,
              typename = std::enable_if_t<!is_not<T, json_ref>::value>,
              typename = std::enable_if_t<!is_not<T, json>::value>,
              bool = std::is_constructible<json, T>::value>
    json_ref(T &&){}
};

I think I failed to think about a possible "infinite"/incomplete template instantiation here when I wrote the initial version of the json_ref code. Before GCC 10 the stdlib metafunctions did not check that and just returned false, making the code work.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CraigHutchinson picture CraigHutchinson  路  4Comments

edi9999 picture edi9999  路  3Comments

zkelo picture zkelo  路  3Comments

bassosimone picture bassosimone  路  3Comments

MariaRamos89 picture MariaRamos89  路  4Comments