mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-30 04:33:53 +00:00
Can add lexical_cast overloads constrained with enable_if (#1021)
Also works for overloads constrained with concepts or requirements. Previously this wasn't working, which is a problem if you want to hook some external serialization framework into CLI11 without painstakingly going through all the types that said external framework supports. This PR is related to https://github.com/CLIUtils/CLI11/issues/908.
This commit is contained in:
parent
6d83f4572d
commit
2e8697cea1
@ -87,6 +87,23 @@ template <> struct IsMemberType<const char *> {
|
|||||||
using type = std::string;
|
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 {
|
namespace detail {
|
||||||
|
|
||||||
// These are utilities for IsMember and other transforming objects
|
// These are utilities for IsMember and other transforming objects
|
||||||
@ -1247,13 +1264,24 @@ bool lexical_cast(const std::string &input, T &output) {
|
|||||||
|
|
||||||
/// Non-string parsable by a stream
|
/// Non-string parsable by a stream
|
||||||
template <typename T,
|
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>
|
detail::enabler> = detail::dummy>
|
||||||
bool lexical_cast(const std::string &input, T &output) {
|
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 "
|
"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");
|
"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
|
/// Assign a value through lexical cast operations
|
||||||
|
@ -283,6 +283,33 @@ TEST_CASE_METHOD(TApp, "custom_string_converter_specialize", "[newparse]") {
|
|||||||
CHECK("something!" == s.s);
|
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
|
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
|
||||||
/// option assignments
|
/// option assignments
|
||||||
template <class X> class objWrapper {
|
template <class X> class objWrapper {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user