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

Added ValueOr functions to ease usage of docopt in -fno-exceptions environment #137

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 68 additions & 4 deletions docopt_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <functional> // std::hash
#include <iosfwd>
#include <stdexcept>
#include <cstdlib> // std::strtol
#include <cerrno> // errno, ERANGE

namespace docopt {

Expand Down Expand Up @@ -51,16 +53,20 @@ namespace docopt {
explicit operator bool() const { return kind_ != Kind::Empty; }

// Test the type contained by this value object
bool isBool() const { return kind_==Kind::Bool; }
bool isString() const { return kind_==Kind::String; }
bool isLong() const { return kind_==Kind::Long; }
bool isStringList() const { return kind_==Kind::StringList; }
bool isBool() const noexcept { return kind_==Kind::Bool; }
bool isString() const noexcept { return kind_==Kind::String; }
bool isLong() const noexcept { return kind_==Kind::Long; }
bool isStringList() const noexcept { return kind_==Kind::StringList; }

// Throws std::invalid_argument if the type does not match
bool asBool() const;
bool asBoolOr(const bool value) const noexcept;
long asLong() const;
long asLongOr(const long value) const noexcept;
std::string const& asString() const;
std::string const& asStringOr(std::string const& value) const noexcept;
std::vector<std::string> const& asStringList() const;
std::vector<std::string> const& asStringListOr(std::vector<std::string> const& value) const noexcept;

size_t hash() const noexcept;

Expand Down Expand Up @@ -274,6 +280,16 @@ namespace docopt {
return variant_.boolValue;
}

inline
bool value::asBoolOr(const bool value) const noexcept
{
if(!isBool())
{
return value;
}
return variant_.boolValue;
}

inline
long value::asLong() const
{
Expand All @@ -292,20 +308,68 @@ namespace docopt {
return variant_.longValue;
}

inline
long value::asLongOr(const long value) const noexcept
{
// Attempt to convert a string to a long
if (isString()) {
const std::string& str = variant_.strValue;
char * str_end;
const long ret = std::strtol(str.c_str(), &str_end, 10);
// Case 1: No Conversion was possible, return given default value
if(str_end == str.c_str() && ret == 0)
{
return value;
}
// Case 2: Parsed value is out of range, return given default value
if (errno == ERANGE) {
return value;
}
// Case 3: Everything is fine, return parsed value
return ret;
}
if (!isLong())
{
return value;
}
return variant_.longValue;
}

Copy link
Member

Choose a reason for hiding this comment

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

Could we maybe put this in a private function to be shared with both? That maybe returns std::optional, and one would throw and the other would not?

Copy link
Author

Choose a reason for hiding this comment

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

Sorry for my late answer. As far as I know std::optional was added to the STL with C++17 so using it here would mean to either raise the required C++ version from C++11 to C++17 or having multiple implementations depending on the C++ version. I think neither is desirable.

What I could see working is either use boost::optional as boost is already a dependency because of boost::regex or using SFINAE to abstract the optional container away like I did in this "simple" example I wrote here https://godbolt.org/z/YPejv9Y71
(gist mirror: https://gist.github.com/Dadie/d06ea6aca53d37142577b4d1ce305246 )

The later would also enable the usage of other container classes aside from boost::optional like std::optional but probably introduce unnecessary complexity and might lead to template code explosion. While the former might break some projects depending on docopt as they may not have boost::optional available on their platform.

While I'm not the maintainer of this library (so it's luckily not me who should decide on it) I'm sort of reluctant in neither solution but am open for opinions.

Half-OT:
On another note I'm quite taken by the idea of porting this library to C++20. I think this type of library would greatly benefit from newer language features like consteval and newer STL classes like std::string_view and/or std::variant. But (sadly a big but) adoption rate of modern C++ is slow and I would guess it will take at least another 5-8 years till we can assume C++20 being the baseline. So switching now to a newer standard would do nothing aside from breaking a lot of projects and making the library literally unusable for a lot of people.

inline
std::string const& value::asString() const
{
throwIfNotKind(Kind::String);
return variant_.strValue;
}

inline
std::string const& value::asStringOr(std::string const& value) const noexcept
{
if(!isString())
{
return value;
}
return variant_.strValue;
}


inline
std::vector<std::string> const& value::asStringList() const
{
throwIfNotKind(Kind::StringList);
return variant_.strList;
}

inline
std::vector<std::string> const& value::asStringListOr(std::vector<std::string> const& value) const noexcept
{
if(!isStringList())
{
return value;
}
return variant_.strList;
}

inline
bool operator==(value const& v1, value const& v2)
{
Expand Down