Skip to content

Commit 2e8697c

Browse files
authored
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 #908.
1 parent 6d83f45 commit 2e8697c

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

include/CLI/TypeTools.hpp

+31-3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,23 @@ template <> struct IsMemberType<const char *> {
8787
using type = std::string;
8888
};
8989

90+
namespace adl_detail {
91+
/// Check for existence of user-supplied lexical_cast.
92+
///
93+
/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail.
94+
/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this
95+
/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem).
96+
template <typename T, typename S = std::string> class is_lexical_castable {
97+
template <typename TT, typename SS>
98+
static auto test(int) -> decltype(lexical_cast(std::declval<const SS &>(), std::declval<TT &>()), std::true_type());
99+
100+
template <typename, typename> static auto test(...) -> std::false_type;
101+
102+
public:
103+
static constexpr bool value = decltype(test<T, S>(0))::value;
104+
};
105+
} // namespace adl_detail
106+
90107
namespace detail {
91108

92109
// These are utilities for IsMember and other transforming objects
@@ -1247,13 +1264,24 @@ bool lexical_cast(const std::string &input, T &output) {
12471264

12481265
/// Non-string parsable by a stream
12491266
template <typename T,
1250-
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value,
1267+
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value &&
1268+
is_istreamable<T>::value,
12511269
detail::enabler> = detail::dummy>
12521270
bool lexical_cast(const std::string &input, T &output) {
1253-
static_assert(is_istreamable<T>::value,
1271+
return from_stream(input, output);
1272+
}
1273+
1274+
/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a
1275+
/// user-supplied lexical_cast overload.
1276+
template <typename T,
1277+
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value &&
1278+
!is_istreamable<T>::value && !adl_detail::is_lexical_castable<T>::value,
1279+
detail::enabler> = detail::dummy>
1280+
bool lexical_cast(const std::string & /*input*/, T & /*output*/) {
1281+
static_assert(!std::is_same<T, T>::value, // Can't just write false here.
12541282
"option object type must have a lexical cast overload or streaming input operator(>>) defined, if it "
12551283
"is convertible from another type use the add_option<T, XC>(...) with XC being the known type");
1256-
return from_stream(input, output);
1284+
return false;
12571285
}
12581286

12591287
/// Assign a value through lexical cast operations

tests/NewParseTest.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,33 @@ TEST_CASE_METHOD(TApp, "custom_string_converter_specialize", "[newparse]") {
283283
CHECK("something!" == s.s);
284284
}
285285

286+
/// Yet another wrapper to test that overloading lexical_cast with enable_if works.
287+
struct yetanotherstring {
288+
yetanotherstring() = default;
289+
std::string s{};
290+
};
291+
292+
template <class T> struct is_my_lexical_cast_enabled : std::false_type {};
293+
294+
template <> struct is_my_lexical_cast_enabled<yetanotherstring> : std::true_type {};
295+
296+
template <class T, CLI::enable_if_t<is_my_lexical_cast_enabled<T>::value, CLI::detail::enabler> = CLI::detail::dummy>
297+
bool lexical_cast(const std::string &input, T &output) {
298+
output.s = input;
299+
return true;
300+
}
301+
302+
TEST_CASE_METHOD(TApp, "custom_string_converter_adl_enable_if", "[newparse]") {
303+
yetanotherstring s;
304+
305+
app.add_option("-s", s);
306+
307+
args = {"-s", "something"};
308+
309+
run();
310+
CHECK("something" == s.s);
311+
}
312+
286313
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
287314
/// option assignments
288315
template <class X> class objWrapper {

0 commit comments

Comments
 (0)