Skip to content

Commit f407d2c

Browse files
captainuristhenryiii
authored andcommitted
Can now add lexical_cast overloads constrained with enable_if, concepts, or requirements.
1 parent dc93d7a commit f407d2c

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

include/CLI/TypeTools.hpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ template <> struct IsMemberType<const char *> {
8585
using type = std::string;
8686
};
8787

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

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

12461263
/// Non-string parsable by a stream
12471264
template <typename T,
1248-
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value,
1265+
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value &&
1266+
is_istreamable<T>::value,
12491267
detail::enabler> = detail::dummy>
12501268
bool lexical_cast(const std::string &input, T &output) {
1251-
static_assert(is_istreamable<T>::value,
1269+
return from_stream(input, output);
1270+
}
1271+
1272+
/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a
1273+
/// user-supplied lexical_cast overload.
1274+
template <typename T,
1275+
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value &&
1276+
!is_istreamable<T>::value && !adl_detail::is_lexical_castable<T>::value,
1277+
detail::enabler> = detail::dummy>
1278+
bool lexical_cast(const std::string & /*input*/, T & /*output*/) {
1279+
static_assert(!std::is_same<T, T>::value, // Can't just write false here.
12521280
"option object type must have a lexical cast overload or streaming input operator(>>) defined, if it "
12531281
"is convertible from another type use the add_option<T, XC>(...) with XC being the known type");
1254-
return from_stream(input, output);
1282+
return false;
12551283
}
12561284

12571285
/// Assign a value through lexical cast operations

tests/NewParseTest.cpp

Lines changed: 27 additions & 0 deletions
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)