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

feat: support std::string_view in Value API #1584

Merged
merged 2 commits into from
Jan 10, 2025

Conversation

evalon32
Copy link
Contributor

@evalon32 evalon32 commented Dec 8, 2024

This adds direct support for std::string_view when available (C++17 and above). The current API can be used with std::string_view via the low-level two-pointer methods, but is not ergonomic. E.g., compare:

Json::Value node;
std::string foo, bar, baz;
std::string_view foo_sv, bar_sv, baz_sv;

// Efficient & readable:
node[foo][bar][baz];

// Less efficient, less readable:
node[std::string(foo_sv)][std::string(bar_sv)][std::string(baz_sv)];

// Efficient, but a lot less readable:
*node.demand(foo_sv.data(), foo_sv.data() + foo_sv.size())
    ->demand(bar_sv.data(), bar_sv.data() + bar_sv.size())
    ->demand(baz_sv.data(), baz_sv.data() + baz_sv.size());

// After this change, efficient & readable:
node[foo_sv][bar_sv][baz_sv];

Change details (only with C++17 or above)

  • The constructor can take a std::string_view parameter. The existing
    overloads taking const std::string& and const char* are still necessary
    to support assignment from those types.
  • operator[], get(), isMember() and removeMember() take a
    std::string_view parameter. This supersedes the overloads taking
    const std::string& and const char*. The overloads taking a pair of
    pointers (begin, end) are preserved for source compatibility.
  • getString() has an overload with a std::string_view output parameter.
    The one with a pair of pointers is preserved for source compatibility.

This adds direct support for `std::string_view` when available (C++17
and above). The current API can be used with `std::string_view` via the
low-level two-pointer methods, but is not ergonomic. E.g., compare:

```
Json::Value node;
std::string foo, bar, baz;
std::string_view foo_sv, bar_sv, baz_sv;

// Efficient & readable:
node[foo][bar][baz];
// Less efficient, less readable:
node[std::string(foo_sv)][std::string(bar_sv)][std::string(baz_sv)];
// Efficient, but a lot less readable:
*node.demand(foo_sv.data(), foo_sv.data() + foo_sv.size())
    ->demand(bar_sv.data(), bar_sv.data() + bar_sv.size())
    ->demand(baz_sv.data(), baz_sv.data() + baz_sv.size())
// After this change, efficient & readable:
node[foo_sv][bar_sv][baz_sv];
```

*   The constructor can take a `std::string_view` parameter. The existing
    overloads taking `const std::string&` and `const char*` are still necessary
    to support assignment from those types.
*   `operator[]`, `get()`, `isMember()` and `removeMember()` take a
    `std::string_view` parameter. This supersedes the overloads taking
    `const std::string&` and `const char*`. The overloads taking a pair of
    pointers (begin, end) are preserved for source compatibility.
*   `getString()` has an overload with a `std::string_view` output parameter.
    The one with a pair of pointers is preserved for source compatibility.

Signed-off-by: Lev Kandel <[email protected]>
@yurykats
Copy link

/assign @baylesj

@@ -1233,12 +1298,18 @@ bool Value::isMember(char const* begin, char const* end) const {
Value const* value = find(begin, end);
return nullptr != value;
}
#ifdef JSONCPP_HAS_STRING_VIEW
bool Value::isMember(std::string_view key) const {
return isMember(key.data(), key.data() + key.length());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this works since it's behind a define and conditionally enabled. Ideally we would be able to have the string_view isMember contain the actual logic and use modern C++ in the find implementation.

@baylesj baylesj merged commit 60ccc1f into open-source-parsers:master Jan 10, 2025
10 checks passed
@bcsgh
Copy link
Contributor

bcsgh commented Jan 26, 2025

FWIW: I suspect this resolved #1352 ??

@beevvy
Copy link
Contributor

beevvy commented Feb 4, 2025

Hi @evalon32, @baylesj,

I just noticed this commit in the master branch, and I think there's a possible problem. jsoncpp is built with -std=c++11 by default, which means that when building json_value.cpp, JSONCPP_HAS_STRING_VIEW is never defined. That means that the newly added member functions won't be part of the resulting library. (I haven't verified it.)

The member functions that are compiled into the library cannot be conditionally enabled. Conditional code should be header-only, for example as inline member functions implemented in terms of other, always enabled member functions.

@evalon32
Copy link
Contributor Author

evalon32 commented Feb 4, 2025

That's a good point -- if the library and its user are compiled with different C++ versions, we'll get linker errors. I'll try to put together a follow-up PR to fix that.

@bcsgh
Copy link
Contributor

bcsgh commented Feb 5, 2025

Outside of plugins, are there actually any cases that really need link compatibility for libraries where the consumer has access to the source? Sure there are legacy reason to continue to support it, but are there any good technical reason for the word at large to not deprecate the cases where people are depending on it?

(There are several large classes of issues that just don't exist in a build-everything-from-source-and-static-link world.)

@beevvy
Copy link
Contributor

beevvy commented Feb 5, 2025

Most Linux distributions ship with a single libjsoncpp.so.26 that's used by ~all packages depending on it. For it to be usable as shared library, the ABI must be predictable. Of course, for Linux distributions' case, you could just hard depend on C++17, but that's another topic...

@bcsgh
Copy link
Contributor

bcsgh commented Feb 6, 2025

@beevvy the only implication of your observation is a need to deal with that reality (and that is a very valid point).

My gripe is that anyone choose to use that .so (or basically any .so expect were the point is to intentionally get different behavior from different .so's) rather than build from source and statically link. My claim is that the cases where anything else is necessarily (or even desirable) are somewhere between few and vanishing.

And FWIW, every package I've worked on that depends on JsonCpp does not depend on libjsoncpp.so and, if I have my way, none ever will.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants