-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
Add conversion from/to std::optional #2117
Conversation
Use JSON_HAS_CPP_17 only after it has been defined
…/nlohmann/json into feature/optional
I am working for checking these CI tasks. Restart the travis CI tasks, and then they would be fine. And about other failed Appveyor CI tasks, I find the difference is |
I restarted all jobs. |
The same AppVeyor jobs keep failing. Any ideas? |
More Info :
It seems that all successful jobs hadn't compile the |
Yes, the |
I find that the What's more, in The actual behavior:
And the test
|
Since I don't use MSVC myself, I cannot debug this further. I will drop this feature from 3.8.0 to proceed. Feel free to dig in, but I will not pursue this issue in the moment. |
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. |
Wow, thanks for the detailed analysis! What do you propose to do? |
I don't see any way to achieve desired conversion sequence for direct initialization when implicit conversions of The above only applies to "conforming" compilers, but each compiler vendor has their own approach to interpretation of standard. It will take time to reach a consensus on subtle topics (and on some trivial topics too). Anyway, it is dangerous to rely on the "new" overload resolution algorithm provided by GCC and Clang with Maybe it is reasonable to support |
Yes, I think this is a reasonable approach. |
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. |
…re/optional � Conflicts: � test/src/unit-conversions.cpp
…re/optional � Conflicts: � test/src/unit-conversions.cpp
std::optional<std::string> opt_null; | ||
|
||
CHECK(json(opt_null) == j_null); | ||
CHECK(std::optional<std::string>(j_null) == std::nullopt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initialization of optional<T>
from empty json
should be considered as a type_error
:
CHECK(std::optional<std::string>(j_null) == std::nullopt); | |
CHECK_THROWS_AS(std::optional<std::string>(j_null), json::type_error&); |
The reason is as follows. If json
is implicitly convertible to T
, then initialization of optional<T>
from json
almost necessarily goes through the "forwarding" constructor:
template <class U> optional(U&& value); // unrelated details omitted
This constructor provides the best parameter/argument match by value category, constness and a type. Its parameter binds directly to json
in any form. The only "weakness" of the forwarding constructor is that it is a template. A conversion function that would be considered as a better candidate for overload resolution must be non-templated, but this is obviously unacceptable.
The forwarding constructor converts json
to T
(initializes the latter from the former) and stores the result, so the constructed optional<T>
cannot be empty. Such a conversion from empty json
to T
is a type error
(even if T
itself is optional<X>
, by induction).
The non-standard overload resolution performed by GCC and Clang when enabling C++17 does not change the outcome. The only way to achieve the "natural" behavior is to tune constructors of std::optional<T>
, which is beyond the scope of the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also the usual way to explicitly convert json
to optional<T>
. The following will work:
CHECK(std::optional<std::string>(j_null) == std::nullopt); | |
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Essentially, there is nothing new here. @dota17 proposed the same in #2213.
However, this approach (treat such initialization as type_error
) introduces some inconsistency:
std::optional<int> opt1 = json().get<std::optional<int>>(); // ok: std::nullopt
std::optional<int> opt2 = std::optional<int>(json()); // throws type_error
std::optional<int> opt3 = json(); // throws type_error
Perhaps it makes sense to throw type_error
in the first case too?
The "natural" conversion could be explicitly implemented by dedicated member function:
template <typename T> std::optional<T> get_optional() const;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moreover, as it turns out, this dedicated member function is the only meaningful thing nlohmann::basic_json
can provide to support std::optional
. Overloads of get<std::optional<T>>
should probably be disabled to prevent confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the behavior is counter-intuitive. JSON's null
is usually considered as "unset" value (in contrast to an empty value). Therefore, I would expect null
to convert to std::nullopt
of any optional type.
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}}; | ||
|
||
CHECK(json(opt_array) == j_array); | ||
CHECK(std::vector<std::optional<int>>(j_array) == opt_array); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direct initialization of vector<...>
is not suitable here:
CHECK(std::vector<std::optional<int>>(j_array) == opt_array); | |
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array); |
Such kind of initialization of vector<...>
has not been tested even with simple item types and actually leads to a compile error for the reason considered earlier. For example, the following will not compile unless using GCC or Clang with C++17 enabled (see https://godbolt.org/z/oeooPG):
json j({1, 2, 3});
std::vector<int> v1 = j; // Copy initialization: ok
auto v2 = std::vector<int>(j); // Direct initialization: compile error
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; | ||
|
||
CHECK(json(opt_object) == j_object); | ||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direct initialization of map<...>
has the same trouble as with vector<...>
above:
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object); | |
CHECK(j_object.get<std::map<std::string, std::optional<int>>>() == opt_object); |
I don't know exactly how this is being implemented but my ideal implementation would be for a field not in the (I'm trying to parse object from Discord's API and there's lots of optional fields. Right now I can't use the |
I'm not sure how to proceed with this PR. Any ideas? |
The primary issue we have is the conversion of |
I don't think this can be directly resolved without edge cases as 'may be null', and 'may be omitted' are distinct concepts in JSON, but people expect I think these need to be separate types in C++ - perhaps a new
This would be similar to:
|
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. |
Is there any updates on this? |
Unfortunately not - any help welcome! |
If I may ask, what is the current blocker for this to go in to the library? I see that the issues with overload resolutions taken up by @karzhenkov seem to still not be addressed by GCC/Clang. From what I understand there is still also discussions about the case of initializing a Also the issues with implicit conversions hell with conversion operators / constructors seem avoidable (?) through I would like to try to contribute to this to help, but I have to admit I'm unsure what I can help with. |
The branch first needs to be rebased to the current |
My two cents:
All choices are then covered: j.get<std::string>(); // must be a string
j.get<std::optional<std::string>>(); // must be a string or null
j.try_get<std::string>(); // may be a string, or may not exist
j.try_get<std::optional<std::string>>(); // may be a string or null, or may not exist Granted, adding a |
Closed in favor of #4036. |
Closes #1749.