Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implicit conversion failure #2128

Closed
gcerretani opened this issue May 20, 2020 · 7 comments
Closed

implicit conversion failure #2128

gcerretani opened this issue May 20, 2020 · 7 comments
Labels
kind: bug platform: visual studio related to MSVC state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated

Comments

@gcerretani
Copy link
Contributor

  • What is the issue you have?
    Strange behavior of implicit conversion

  • Please describe the steps to reproduce the issue. Can you provide a small but working code example?

#include <cstdlib>
#include <string>
#include <list>
#include <boost/range/algorithm/transform.hpp>
#include <nlohmann/json.hpp>
#include <type_traits>

struct my_struct {
	enum class names { UNKNOWN, FOO, BAR };
};

NLOHMANN_JSON_SERIALIZE_ENUM(my_struct::names, {
	{ my_struct::names::UNKNOWN, 			nullptr },
	{ my_struct::names::FOO, 				"FOO" },
	{ my_struct::names::BAR, 				"BAR" }
})

template <typename T>
struct json_data_format {

	json_data_format()
		: _name{ names::UNKNOWN } {
	}

	template <typename... Args>
	static json_data_format marshal(Args&& ...args) {
		return nlohmann::json::parse(std::forward<Args>(args)...);
	}

	nlohmann::json::string_t unmarshal() const {
		return nlohmann::json(*this).dump();
	}

	~json_data_format() = default;
	json_data_format(const json_data_format&) = default;
	json_data_format(json_data_format&&) = default;
	json_data_format& operator=(const json_data_format&) = default;
	json_data_format& operator=(json_data_format&&) = default;

	auto get_name() const noexcept { return _name; }

	friend void from_json(const nlohmann::json& j, json_data_format& e) {
		const auto get_if_not_null = [&j](const char* key, auto& value) {
			try {
				const auto j_value = j.at(key);
				if (!j_value.is_null())
					j_value.get_to(value);
			}
			catch (const nlohmann::json::out_of_range&) {}
		};
		get_if_not_null(json_data_format::key_name, e._name);
	}

	friend void to_json(nlohmann::json& j, const json_data_format& e) {
		const auto set = [&j](const char* key, const auto& value) {
			j.update(nlohmann::json::object({ { key, value } }));
		};
		set(json_data_format::key_name, e._name);
	}

	static constexpr const char* key_name = "name";

private:
	using names = typename T::names;
	names _name;
};

using namespace std::string_literals;

int main() {
	auto j = nlohmann::json::parse(R"([ { "name" : "FOO" }, { "name" : "BAR" } ])");
	std::list<my_struct::names> _args_list;
	boost::transform(j, std::back_inserter(_args_list), [](const nlohmann::json& element) {
		const json_data_format<my_struct> format = element; // implicit conversion
		return format.get_name();
	});
	return EXIT_SUCCESS;
}
  • What is the expected behavior?
    The json is correctly parsed.

  • And what is the actual behavior instead?
    The parse fails because the lambda get_if_not_null is called with nullptr as first argument (the const char* key). The code works correctly if the implicit conversion is replaced by an explicit conversion with auto format = element.get<json_data_format<my_struct>>();

  • Which compiler and operating system are you using? Is it a supported compiler?
    Microsoft Visual C++ 2015 / Build Tools 14.0.25431.01, currently supported. I've tried also with GCC 7 and everything seems to work as expected.

  • Did you use a released version of the library or the version from the develop branch?
    v3.7.3

@nlohmann
Copy link
Owner

  • What is the exact error message?
  • You write "the parse fails" - does the line auto j = nlohmann::json::parse(R"([ { "name" : "FOO" }, { "name" : "BAR" } ])"); fail?
  • Can you provide an example without boost?

I tried the code with Xcode after removing the boost stuff. This works:

    auto j = nlohmann::json::parse(R"([ { "name" : "FOO" }, { "name" : "BAR" } ])");
    
    my_struct::names n1 = j[0]["name"];
    my_struct::names n2 = j[1]["name"];
    
    const json_data_format<my_struct> format = j[0];

@nlohmann nlohmann added the state: needs more info the author of the issue needs to provide more details label May 20, 2020
@gcerretani
Copy link
Contributor Author

gcerretani commented May 20, 2020

  • The exact error message of the debugger is: Exception thrown: read access violation. key was nullptr.

  • The line that fails is const json_data_format<my_struct> format = element;, that works if replaced with auto format = element.get<json_data_format<my_struct>>();

  • Yes, I know that minor changes make of the code to work properly. For example, if you add a call to const json_data_format<my_struct> format = j.front(); just before the call to transform, everything seems to work. Boost is not the problem, you should get the same by replacing boost::transform(j, ...) with std::transform(j.begin(), j.end(), ...). Actually it seems a bug in the MSVC2015 compiler.

@gcerretani
Copy link
Contributor Author

FYI, the code works perfectly with Visual Studio 2019 (tried with 16.5.4). Probably it is really a bug in the Visual Studio 2015 compiler/linker. Unfortunately, I've not been able to reproduce a simpler example.

@nlohmann nlohmann added platform: visual studio related to MSVC and removed state: needs more info the author of the issue needs to provide more details labels May 22, 2020
@dota17
Copy link
Contributor

dota17 commented May 25, 2020

FYI, the code works perfectly with Visual Studio 2019

It also works in my env - Visual Studio 2019.

@dota17
Copy link
Contributor

dota17 commented May 26, 2020

Beats me. I try to install vs2015 and reproduce, but none is smooth.

@gcerretani
Copy link
Contributor Author

gcerretani commented May 26, 2020

Other interesting things I've seen:

  • If I replace the lambda with a static function, passing j as reference, the problem is still there:
template <typename T>
void get_if_not_null(const nlohmann::json& j, const char* key, T& value) try {
	const auto j_value = j.at(key);
	if (!j_value.is_null())
		j_value.get_to(value);
}
catch (const nlohmann::json::out_of_range&) {}
  • If I change the type of key_name from static constexpr const char* to static constexpr const char[], the function is then called with a non null pointer, which actually points to a zero byte (empty C string). Changing the implicit conversion to a call fo the get function (auto format = element.get<json_data_format<my_struct>>(), as explaned in the first post), the function is then called with a pointer that now contains "name". I think this behavior is even worse, because now the program runs fine, since the key is a valid string, while with a nullptr is was crashing trying to initialize a string from nullptr.

Unfortunately godbolt does not support Visual Studio 2015 (even if I've seen a MSVC 2015 (WINE), but I'm not sure is the same). However, if needed, I've pasted my code in this godbolt notebook.

@stale
Copy link

stale bot commented Jun 26, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Jun 26, 2020
@stale stale bot closed this as completed Jul 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: bug platform: visual studio related to MSVC state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated
Projects
None yet
Development

No branches or pull requests

3 participants