mirror of
https://github.com/CLIUtils/CLI11.git
synced 2026-01-19 04:52:08 +00:00
feat: support for Atomic types in flags and options (#520)
This commit is contained in:
@@ -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 = "") {
|
||||
@@ -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,
|
||||
@@ -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 = "") {
|
||||
|
||||
@@ -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)) {
|
||||
@@ -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 "
|
||||
@@ -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>
|
||||
@@ -1052,9 +1095,46 @@ 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 for int compatible values
|
||||
/// mainly for atomic operations on some compilers
|
||||
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,
|
||||
@@ -1366,10 +1446,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{};
|
||||
@@ -1383,6 +1464,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
|
||||
@@ -1411,5 +1512,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
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "app_helper.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <climits>
|
||||
#include <complex>
|
||||
#include <cstdint>
|
||||
@@ -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) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#include "app_helper.hpp"
|
||||
#include <atomic>
|
||||
#include <complex>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user