json icon indicating copy to clipboard operation
json copied to clipboard

[MSVC][build] JSON failed with error C2672: 'nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,

Open QuellaZhang opened this issue 2 months ago • 8 comments

Description

Hi All,

The MSVC team regularly builds JSON to detect the regressions in the compiler, recently we updated JSON source code to the latest and found this issue. In addition, the issue was not reproduced after adding the /permissive-.

Build.log

Reproduction steps

  1. Open VS2026 x64 Native tools command prompt
  2. git clone https://github.com/nlohmann/json.git C:\gitP\nlohmann\json
  3. mkdir C:\gitP\nlohmann\json\build_amd64 & cd C:\gitP\nlohmann\json\build_amd64
  4. cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_SYSTEM_VERSION=10.0.26100.0 -DJSON_BuildTests=On ..
  5. msbuild /m /p:Platform=x64 /p:Configuration=Release nlohmann_json.sln /t:Rebuild

Expected vs. actual results

Expected build successful.

Minimal code example

The problem occurs with this code:
++
        json j;
        const json::array_t a = j;
        json const j_string = "Path";
        auto p = j_string.template get<nlohmann::detail::std_fs::path>(); // fails

Error messages

unit-conversions.cpp
C:\gitP\nlohmann\json\tests\src\unit-conversions.cpp(1667): error C2672: 'nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get': no matching overloaded function found
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1813): note: could be 'unknown-type nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get(void) noexcept'
C:\gitP\nlohmann\json\tests\src\unit-conversions.cpp(1667): note: 'unknown-type nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get(void) noexcept': could not deduce template argument for '__formal'
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1812): note: 'type': is not a member of any direct or indirect base class of 'std::enable_if<false,int>'
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1772): note: or       'unknown-type nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get(void) noexcept(<expr>) const'
C:\gitP\nlohmann\json\tests\src\unit-conversions.cpp(1667): note: Failed to specialize function template 'unknown-type nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get(void) noexcept(<expr>) const'
C:\gitP\nlohmann\json\tests\src\unit-conversions.cpp(1667): note: With the following template arguments:
C:\gitP\nlohmann\json\tests\src\unit-conversions.cpp(1667): note: 'ValueTypeCV=std::filesystem::path'
C:\gitP\nlohmann\json\tests\src\unit-conversions.cpp(1667): note: 'ValueType=std::filesystem::path'
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1774): note: 'nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get_impl': no matching overloaded function found
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1737): note: could be 'unknown-type nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get_impl(nlohmann::json_abi_v3_12_0::detail::priority_tag<4>) noexcept const'
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1724): note: or       'nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void> nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get_impl(nlohmann::json_abi_v3_12_0::detail::priority_tag<3>) const'
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1701): note: or       'BasicJsonType nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get_impl(nlohmann::json_abi_v3_12_0::detail::priority_tag<2>) const'
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1676): note: or       'ValueType nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get_impl(nlohmann::json_abi_v3_12_0::detail::priority_tag<1>) noexcept(<expr>) const'
C:\gitP\nlohmann\json\include\nlohmann/json.hpp(1634): note: or       'ValueType nlohmann::json_abi_v3_12_0::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_12_0::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>,void>::get_impl(nlohmann::json_abi_v3_12_0::detail::priority_tag<0>) noexcept(<expr>) const'
C:\gitP\nlohmann\json\tests\src\unit-conversions.cpp(1668): error C3536: 'p': cannot be used before it is initialized

Compiler and operating system

VS2026

Library version

3ed64e5

Validation

QuellaZhang avatar Oct 30 '25 10:10 QuellaZhang

Not using MSVC myself, so I'd be glad if anyone could help.

nlohmann avatar Oct 30 '25 10:10 nlohmann

@QuellaZhang Just so I understand: the code does compile with previous versions of MSVC, but fails to do so with a newer version unless /permisisve- is used?

nlohmann avatar Oct 31 '25 09:10 nlohmann

For those not familiar, that flag has a typo, it's /permissive-

Since this flag means "disable non-standard extensions", I think it's likely something that can't be cleanly fixed.

gregmarr avatar Oct 31 '25 13:10 gregmarr

It seems this might warrant opening a ticket on MSVC’s side. When building with /permissive-, the code compiles successfully, which suggests that the failure occurs due to MSVC 2026’s non–standard-conforming behavior rather than an issue with the code itself. From what I can tell, the code appears to be standard-compliant.

For reference, I also tested with std:c++20, and the code compiles even without /permissive-. This indicates the problem is specific to how MSVC 2026 handles C++17.

Out of curiosity, I asked Claude.ai for an analysis. I’m not deeply familiar with advanced template mechanics, so I can’t fully validate its reasoning, and none of the suggested code-based workarounds it proposed later worked. Please note: the following quote is AI-generated and may be inaccurate.

The compiler evaluates the noexcept and decltype specifications before fully resolving all the SFINAE constraints in the get_impl overloads This can cause the SFINAE evaluation to fail prematurely or evaluate in the wrong order The detail::priority_tag<4> mechanism (which is designed to select overloads in priority order) doesn't work correctly

With /permissive-:

Two-phase lookup is properly enforced The noexcept and decltype expressions are evaluated at the correct phase The SFINAE constraints in each get_impl overload are checked in the proper order (4 → 3 → 2 → 1 → 0)

Why std::filesystem::path Specifically Fails The priority_tag<4> overload is for pointers (std::is_pointer<PointerType>::value). In non-conforming mode, when MSVC evaluates the decltype for std::filesystem::path, it might:

Try the priority_tag<4> overload first (pointer check fails) But due to incorrect evaluation order, it doesn't properly fall through to the lower priority overloads The SFINAE constraint std::enable_if<false, int> fires incorrectly

The Solution This is a bug in how MSVC's non-conforming mode handles complex noexcept/decltype SFINAE patterns. The nlohmann JSON library is written correctly according to the C++ standard. Recommendation: Use /permissive- in MSVC 2026. The non-conforming mode is deprecated and will likely be removed in future versions. The library code is correct; it's MSVC's legacy behavior that's broken.

PerretDavid avatar Nov 18 '25 11:11 PerretDavid

It seems this might warrant opening a ticket on MSVC’s side. When building with /permissive-, the code compiles successfully, which suggests that the failure occurs due to MSVC 2026’s non–standard-conforming behavior rather than an issue with the code itself. From what I can tell, the code appears to be standard-compliant.

Right, this is running into a very old MSVC extension/bug that is disabled in /permissive- mode (permissive- is implied by c++20 or later). Opening a ticket isn't likely to be very helpful, as we'd have removed this extension entirely if we could. There is a lot of old, non-conforming code that relies on it that we need to keep building (that AI summary is incorrect, as much as I'd like it to be permissive mode is not deprecated and its unclear when or if we'll be able to finally deprecate it). We do recommend everyone use /permissive- unless they have need of these old behaviors.

The bug/extension in question allows two user-defined conversions in a single conversion. This causes the operator ValueType to be considered and specialized in situations where it shouldn't be a candidate. Specializing that can wind up in a place where evaluating the SFINAE constraint on it tries to form a function template specialization that needs to recursively resolve its own type and fails.

joemmett avatar Nov 20 '25 13:11 joemmett

It seems this might warrant opening a ticket on MSVC’s side. When building with /permissive-, the code compiles successfully, which suggests that the failure occurs due to MSVC 2026’s non–standard-conforming behavior rather than an issue with the code itself. From what I can tell, the code appears to be standard-compliant.

Right, this is running into a very old MSVC extension/bug that is disabled in /permissive- mode (permissive- is implied by c++20 or later). Opening a ticket isn't likely to be very helpful, as we'd have removed this extension entirely if we could. There is a lot of old, non-conforming code that relies on it that we need to keep building (that AI summary is incorrect, as much as I'd like it to be permissive mode is not deprecated and its unclear when or if we'll be able to finally deprecate it). We do recommend everyone use /permissive- unless they have need of these old behaviors.

The bug/extension in question allows two user-defined conversions in a single conversion. This causes the operator ValueType to be considered and specialized in situations where it shouldn't be a candidate. Specializing that can wind up in a place where evaluating the SFINAE constraint on it tries to form a function template specialization that needs to recursively resolve its own type and fails.

Thank you for clarifying! I’m curious, why did it work with VS 2022 if it’s related to such an old extension or bug? Was there some kind of workaround that was present in that version and not in the new one ?

PerretDavid avatar Nov 20 '25 14:11 PerretDavid

We fixed a different bug -- one that would too-eagerly exclude the templated operator during reference binding. A conversion operator of type T is not a candidate when converting to an lvalue. But a templated conversion to a templated type T could be a candidate if it was specialized with T as some lvalue-reference type, we can't exclude it until after type-replacement.

joemmett avatar Nov 20 '25 14:11 joemmett

@QuellaZhang Just so I understand: the code does compile with previous versions of MSVC, but fails to do so with a newer version unless /permissive- is used?

@nlohmann, Yes, and I think Jonathan has already explained why it appeared in the new version of MSVC.

QuellaZhang avatar Nov 21 '25 07:11 QuellaZhang