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;
|
||||
};
|
||||
|
||||
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
|
||||
@ -1247,13 +1264,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
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user