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

Suggestion to improve value() accessors with respect to move semantics #1275

Closed
murderotica opened this issue Oct 4, 2018 · 9 comments · Fixed by #2181
Closed

Suggestion to improve value() accessors with respect to move semantics #1275

murderotica opened this issue Oct 4, 2018 · 9 comments · Fixed by #2181
Labels
state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated

Comments

@murderotica
Copy link

Here's a couple suggestions that I think might be worth adding to improve the usage of value() function:

  1. Use "universal reference" && instead of const& for default_value to lift a problem that const ValueType& default_value inhibits move when default_value is temporary/r-value:

Before:

template<class ValueType, typename std::enable_if<
                 std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
    ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const
    {
        // at only works for objects
        if (JSON_LIKELY(is_object()))
        {
            // if key is found, return value and given default value otherwise
            const auto it = find(key);
            if (it != end())
            {
                return *it;
            }

            return default_value;
        }

        JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
}

After

template<class ValueType, typename std::enable_if<
                 std::is_convertible<basic_json_t, detail::uncvref_t<ValueType>>::value, int>::type = 0>
    detail::uncvref_t<ValueType> value(const typename object_t::key_type& key, ValueType&& default_value) const &
    {
        // at only works for objects
        if (JSON_LIKELY(is_object()))
        {
            // if key is found, return value and given default value otherwise
            const auto it = find(key);
            if (it != end())
            {
                return *it;
            }
            return std::forward<ValueType>(default_value);
        }

        JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
    }

This will allow this:

json j{{"x", "test"}};
std::string defval = "default value";
auto val = j.value("y", std::move(defval) /* or a temporary constructed in-place */);

// defval is now empty (moved-from) and val contains "default value"
  1. Provide r-value reference overload for value() to allow moving from JSON's values when JSON is temporary/r-value:
template<class ValueType, typename std::enable_if<
                 std::is_convertible<basic_json_t, detail::uncvref_t<ValueType>>::value, int>::type = 0>
    detail::uncvref_t<ValueType> value(const typename object_t::key_type& key, ValueType&& default_value) &&
    {
        // at only works for objects
        if (JSON_LIKELY(is_object()))
        {
            // if key is found, return value and given default value otherwise
            const auto it = find(key);
            if (it != end())
            {
                return std::move(it->template get_ref<ValueType&>());
            }

            return std::forward<ValueType>(default_value);
        }

        JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
    }

This will make possible the following:

json j{{"x", "123"}};
auto val = std::move(j).value("x", std::string{"whatever"});

// j["x"] in now empty (moved-from) and val contains "123"
@murderotica
Copy link
Author

One more thing: it may also be useful to provide r-value reference overload to the conversion operator ValueType() to allow moving elements when resulting type is not basic_json:

Currently, this doesn't do what one might expect:

json j{{"x", "123"}};
std::string val = std::move(j["x"]);
// oops, j["x"] still has the value "123" - we just copied stuff even though wanted to move
// we have to do this to make it work, cumbersome
std::string val = std::move(j["x"].get_ref<std::string&>());

@jaredgrubb
Copy link
Contributor

+1 on the first point. Forwarding reference definitely makes sense there.

I don't agree on the second one. I could see an argument for it to return a "ValueType&&", but returning a "ValueType" with a side-effect of changing the underlying json object feels "surprising" to me (and I don't like surprises in interfaces :)).

@murderotica
Copy link
Author

murderotica commented Oct 4, 2018

I don't agree on the second one. I could see an argument for it to return a "ValueType&&", but returning a "ValueType" with a side-effect of changing the underlying json object feels "surprising" to me (and I don't like surprises in interfaces :)).

You can't return ValueType&& since it won't do what I would expected it to after doing move(json).value(...) because the return type here is deduced at the same time as the type of default_value (that is, if ValueType is an l-value ref then you will return an l-value ref and copy will occur instead of move, what's more, is that once you will try to use and l-value ref as default_value you will not be able to perfom std::move() on the existing value, because you can't bind lvalue ref to rvalue). While return by value will actually work as expected. Remember that it is an r-value overload.

@jaredgrubb
Copy link
Contributor

jaredgrubb commented Oct 4, 2018

I wrote "ValueType&&" as shorthand, sorry for being imprecise. I meant that it really should be returning rvalue in all cases (aka, detail::uncvref_t<ValueType>&& value(..., ValueType&&) &&), but you're right that if ValueType&& == T const& then you have a binding problem.

You could solve this by adding additional overloads to trap those, but then you get very different behavior depending on what type your second argument is, and that is also a "surprising" API to me.

All in all, I like the spirit of your (2) suggestion, but I don't see a way to get all the benefits without adding surprises too. The idea that a function named "value(key,def)&&" has side effects is surprising to me as a code-maintainer. If you rename your function to "pop_value(key,def)&&" that returns value type (exactly as you proposed), then I am totally on board.

@nlohmann
Copy link
Owner

I like the original proposal from @murderotica. Could you provide a PR?

@stale
Copy link

stale bot commented Nov 24, 2018

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 Nov 24, 2018
@stale stale bot closed this as completed Dec 1, 2018
@dota17 dota17 mentioned this issue Jun 10, 2020
4 tasks
@nlohmann nlohmann reopened this Jun 23, 2020
@stale stale bot removed the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Jun 23, 2020
@nlohmann nlohmann linked a pull request Jun 23, 2020 that will close this issue
4 tasks
@nlohmann nlohmann added solution: proposed fix a fix for the issue has been proposed and waits for confirmation and removed state: waiting for PR labels Jun 23, 2020
@nlohmann nlohmann self-assigned this Jun 23, 2020
@nlohmann nlohmann added this to the Release 3.8.1 milestone Jun 23, 2020
@nlohmann nlohmann reopened this Jul 8, 2020
@nlohmann
Copy link
Owner

nlohmann commented Jul 8, 2020

Reopened due to bug found. See #2181.

@nlohmann nlohmann removed release item: 🔨 further change solution: proposed fix a fix for the issue has been proposed and waits for confirmation labels Jul 8, 2020
@nlohmann nlohmann removed this from the Release 3.8.1 milestone Jul 8, 2020
@nlohmann
Copy link
Owner

nlohmann commented Jul 8, 2020

PR #2251 reverts #2181. Happy to see PRs to fix this issue once #2251 is merged.

@nlohmann nlohmann removed their assignment Jul 11, 2020
@stale
Copy link

stale bot commented Aug 16, 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 Aug 16, 2020
@stale stale bot closed this as completed Aug 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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

Successfully merging a pull request may close this issue.

3 participants