Skip to content

Commit

Permalink
Support for Atomic types in flags and options
Browse files Browse the repository at this point in the history
  • Loading branch information
phlptp committed Oct 5, 2020
1 parent 438eabe commit a91dc1f
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 12 deletions.
11 changes: 6 additions & 5 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,8 @@ class App {
/// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one
/// if `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
template <typename T,
enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy>
enable_if_t<std::is_constructible<T, std::int64_t>::value && !is_bool<T>::value, detail::enabler> =
detail::dummy>
Option *add_flag(std::string flag_name,
T &flag_count, ///< A variable holding the count
std::string flag_description = "") {
Expand All @@ -810,7 +811,7 @@ class App {
/// that can be converted from a string
template <typename T,
enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
(!std::is_integral<T>::value || is_bool<T>::value) &&
(!std::is_constructible<T, std::int64_t>::value || is_bool<T>::value) &&
!std::is_constructible<std::function<void(int)>, T>::value,
detail::enabler> = detail::dummy>
Option *add_flag(std::string flag_name,
Expand All @@ -824,9 +825,9 @@ class App {
}

/// Vector version to capture multiple flags.
template <
typename T,
enable_if_t<!std::is_assignable<std::function<void(std::int64_t)>, T>::value, detail::enabler> = detail::dummy>
template <typename T,
enable_if_t<!std::is_assignable<std::function<void(std::int64_t)> &, T>::value, detail::enabler> =
detail::dummy>
Option *add_flag(std::string flag_name,
std::vector<T> &flag_results, ///< A vector of values with the flag results
std::string flag_description = "") {
Expand Down
141 changes: 134 additions & 7 deletions include/CLI/TypeTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,22 @@ bool lexical_cast(const std::string &input, T &output) {

/// wrapper types
template <typename T,
enable_if_t<classify_object<T>::value == object_category::wrapper_value, detail::enabler> = detail::dummy>
enable_if_t<classify_object<T>::value == object_category::wrapper_value &&
std::is_assignable<T &, typename T::value_type>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
typename T::value_type val;
if(lexical_cast(input, val)) {
output = val;
return true;
}
return from_stream(input, output);
}

template <typename T,
enable_if_t<classify_object<T>::value == object_category::wrapper_value &&
!std::is_assignable<T &, typename T::value_type>::value && std::is_assignable<T &, T>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
typename T::value_type val;
if(lexical_cast(input, val)) {
Expand Down Expand Up @@ -1019,8 +1034,36 @@ bool lexical_cast(const std::string &input, T &output) {
return from_stream(input, output);
}

/// Non-string convertible from an int
template <typename T,
enable_if_t<classify_object<T>::value == object_category::other && std::is_assignable<T &, int>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
int val;
if(integral_conversion(input, val)) {
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4800)
#endif
// with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style
// so will most likely still work
output = val;
#ifdef _MSC_VER
#pragma warning(pop)
#endif
return true;
}
/// LCOV_EXCL_START
// This version of cast is only used for odd cases in an older compilers the fail over
// from_stream is tested elsewhere an not relevent for coverage here
return from_stream(input, output);
// LCOV_EXCL_STOP
}

/// Non-string parsable by a stream
template <typename T, enable_if_t<classify_object<T>::value == object_category::other, detail::enabler> = detail::dummy>
template <typename T,
enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
static_assert(is_istreamable<T>::value,
"option object type must have a lexical cast overload or streaming input operator(>>) defined, if it "
Expand All @@ -1043,7 +1086,7 @@ bool lexical_assign(const std::string &input, AssignTo &output) {
/// Assign a value through lexical cast operations
template <typename AssignTo,
typename ConvertTo,
enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value &&
classify_object<AssignTo>::value != object_category::string_assignable &&
classify_object<AssignTo>::value != object_category::string_constructible,
detail::enabler> = detail::dummy>
Expand All @@ -1052,9 +1095,45 @@ bool lexical_assign(const std::string &input, AssignTo &output) {
output = AssignTo{};
return true;
}

return lexical_cast(input, output);
}

/// Assign a value through lexical cast operations
template <typename AssignTo,
typename ConvertTo,
enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value &&
classify_object<AssignTo>::value == object_category::wrapper_value,
detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, AssignTo &output) {
if(input.empty()) {
typename AssignTo::value_type emptyVal{};
output = emptyVal;
return true;
}
return lexical_cast(input, output);
}

/// Assign a value through lexical cast operations
template <typename AssignTo,
typename ConvertTo,
enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value &&
classify_object<AssignTo>::value != object_category::wrapper_value &&
std::is_assignable<AssignTo &, int>::value,
detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, AssignTo &output) {
if(input.empty()) {
output = 0;
return true;
}
int val;
if(lexical_cast(input, val)) {
output = val;
return true;
}
return false;
}

/// Assign a value converted from a string in lexical cast to the output value directly
template <typename AssignTo,
typename ConvertTo,
Expand Down Expand Up @@ -1366,10 +1445,11 @@ bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &outp
}

/// conversion for wrapper types
template <
typename AssignTo,
class ConvertTo,
enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value, detail::enabler> = detail::dummy>
template <typename AssignTo,
class ConvertTo,
enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value &&
std::is_assignable<ConvertTo &, ConvertTo>::value,
detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
if(strings.empty() || strings.front().empty()) {
output = ConvertTo{};
Expand All @@ -1383,6 +1463,26 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu
return false;
}

/// conversion for wrapper types
template <typename AssignTo,
class ConvertTo,
enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value &&
!std::is_assignable<AssignTo &, ConvertTo>::value,
detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
using ConvertType = typename ConvertTo::value_type;
if(strings.empty() || strings.front().empty()) {
output = ConvertType{};
return true;
}
ConvertType val;
if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) {
output = val;
return true;
}
return false;
}

/// Sum a vector of flag representations
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
/// by
Expand Down Expand Up @@ -1411,5 +1511,32 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
output = static_cast<T>(count);
}

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4800)
#endif
// with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style so will
// most likely still work

/// Sum a vector of flag representations
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
/// by
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
/// common true and false strings then uses stoll to convert the rest for summing
template <typename T,
enable_if_t<!std::is_signed<T>::value && !std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
std::int64_t count{0};
for(auto &flag : flags) {
count += detail::to_flag_value(flag);
}
std::string out = detail::to_string(count);
lexical_cast(out, output);
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif

} // namespace detail
} // namespace CLI
3 changes: 3 additions & 0 deletions tests/HelpersTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "app_helper.hpp"

#include <array>
#include <atomic>
#include <climits>
#include <complex>
#include <cstdint>
Expand Down Expand Up @@ -991,6 +992,8 @@ TEST(Types, TypeName) {
EXPECT_EQ("ENUM", enum_name2);
std::string umapName = CLI::detail::type_name<std::unordered_map<int, std::tuple<std::string, double>>>();
EXPECT_EQ("[INT,[TEXT,FLOAT]]", umapName);

vclass = CLI::detail::classify_object<std::atomic<int>>::value;
}

TEST(Types, OverflowSmall) {
Expand Down
48 changes: 48 additions & 0 deletions tests/OptionTypeTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// SPDX-License-Identifier: BSD-3-Clause

#include "app_helper.hpp"
#include <atomic>
#include <complex>
#include <cstdint>
#include <cstdlib>
Expand Down Expand Up @@ -141,6 +142,33 @@ TEST_F(TApp, BoolAndIntFlags) {
EXPECT_EQ((unsigned int)2, uflag);
}

TEST_F(TApp, atomic_bool_flags) {

std::atomic<bool> bflag{false};
std::atomic<int> iflag{0};

app.add_flag("-b", bflag);
app.add_flag("-i,--int", iflag);

args = {"-b", "-i"};
run();
EXPECT_TRUE(bflag.load());
EXPECT_EQ(1, iflag.load());

args = {"-b", "-b"};
ASSERT_NO_THROW(run());
EXPECT_TRUE(bflag.load());

bflag = false;

args = {"-iii"};
run();
EXPECT_FALSE(bflag.load());
EXPECT_EQ(3, iflag.load());
args = {"--int=notanumber"};
EXPECT_THROW(run(), CLI::ConversionError);
}

TEST_F(TApp, BoolOption) {
bool bflag{false};
app.add_option("-b", bflag);
Expand All @@ -167,6 +195,26 @@ TEST_F(TApp, BoolOption) {
EXPECT_FALSE(bflag);
}

TEST_F(TApp, atomic_int_option) {
std::atomic<int> i{0};
auto aopt = app.add_option("-i,--int", i);
args = {"-i4"};
run();
EXPECT_EQ(1u, app.count("--int"));
EXPECT_EQ(1u, app.count("-i"));
EXPECT_EQ(i, 4);
EXPECT_EQ(app["-i"]->as<std::string>(), "4");
EXPECT_EQ(app["--int"]->as<double>(), 4.0);

args = {"--int", "notAnInt"};
EXPECT_THROW(run(), CLI::ConversionError);

aopt->expected(0, 1);
args = {"--int"};
run();
EXPECT_EQ(i, 0);
}

TEST_F(TApp, CharOption) {
char c1{'t'};
app.add_option("-c", c1);
Expand Down

0 comments on commit a91dc1f

Please sign in to comment.