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

Can add lexical_cast overloads constrained with enable_if #1021

Merged
merged 1 commit into from
May 2, 2024
Merged
Show file tree
Hide file tree
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
34 changes: 31 additions & 3 deletions include/CLI/TypeTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ template <> struct IsMemberType<const char *> {
using type = std::string;
};

namespace adl_detail {
/// Check for existence of user-supplied lexical_cast.
///
/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail.
/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this
/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem).
template <typename T, typename S = std::string> class is_lexical_castable {
template <typename TT, typename SS>
static auto test(int) -> decltype(lexical_cast(std::declval<const SS &>(), std::declval<TT &>()), std::true_type());

template <typename, typename> static auto test(...) -> std::false_type;

public:
static constexpr bool value = decltype(test<T, S>(0))::value;
};
} // namespace adl_detail

namespace detail {

// These are utilities for IsMember and other transforming objects
Expand Down Expand Up @@ -1245,13 +1262,24 @@ bool lexical_cast(const std::string &input, T &output) {

/// Non-string parsable by a stream
template <typename T,
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value,
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value &&
is_istreamable<T>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
static_assert(is_istreamable<T>::value,
return from_stream(input, output);
}

/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a
/// user-supplied lexical_cast overload.
template <typename T,
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value &&
!is_istreamable<T>::value && !adl_detail::is_lexical_castable<T>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string & /*input*/, T & /*output*/) {
static_assert(!std::is_same<T, T>::value, // Can't just write false here.
"option object type must have a lexical cast overload or streaming input operator(>>) defined, if it "
"is convertible from another type use the add_option<T, XC>(...) with XC being the known type");
return from_stream(input, output);
return false;
}

/// Assign a value through lexical cast operations
Expand Down
27 changes: 27 additions & 0 deletions tests/NewParseTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,33 @@ TEST_CASE_METHOD(TApp, "custom_string_converter_specialize", "[newparse]") {
CHECK("something!" == s.s);
}

/// Yet another wrapper to test that overloading lexical_cast with enable_if works.
struct yetanotherstring {
yetanotherstring() = default;
std::string s{};
};

template <class T> struct is_my_lexical_cast_enabled : std::false_type {};

template <> struct is_my_lexical_cast_enabled<yetanotherstring> : std::true_type {};

template <class T, CLI::enable_if_t<is_my_lexical_cast_enabled<T>::value, CLI::detail::enabler> = CLI::detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
output.s = input;
return true;
}

TEST_CASE_METHOD(TApp, "custom_string_converter_adl_enable_if", "[newparse]") {
yetanotherstring s;

app.add_option("-s", s);

args = {"-s", "something"};

run();
CHECK("something" == s.s);
}

/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
/// option assignments
template <class X> class objWrapper {
Expand Down
Loading