Skip to content

Commit 6cd171a

Browse files
get_subcommand_no_throw (#1016)
get_subcommand when used for parsing config files, was throwing and catching as part of control flow and expected operation, this resulting in a performance hit in select cases. A get_subcommand_no_throw was added to resolve this issue. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent c04c9b2 commit 6cd171a

File tree

3 files changed

+19
-12
lines changed

3 files changed

+19
-12
lines changed

include/CLI/App.hpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,10 @@ class App {
733733
/// Check to see if a subcommand is part of this command (text version)
734734
CLI11_NODISCARD App *get_subcommand(std::string subcom) const;
735735

736+
/// Get a subcommand by name (noexcept non-const version)
737+
/// returns null if subcommand doesn't exist
738+
CLI11_NODISCARD App *get_subcommand_no_throw(std::string subcom) const noexcept;
739+
736740
/// Get a pointer to subcommand by index
737741
CLI11_NODISCARD App *get_subcommand(int index = 0) const;
738742

@@ -907,8 +911,9 @@ class App {
907911
}
908912

909913
/// Check with name instead of pointer to see if subcommand was selected
910-
CLI11_NODISCARD bool got_subcommand(std::string subcommand_name) const {
911-
return get_subcommand(subcommand_name)->parsed_ > 0;
914+
CLI11_NODISCARD bool got_subcommand(std::string subcommand_name) const noexcept {
915+
App *sub = get_subcommand_no_throw(subcommand_name);
916+
return (sub != nullptr) ? (sub->parsed_ > 0) : false;
912917
}
913918

914919
/// Sets excluded options for the subcommand
@@ -1038,7 +1043,7 @@ class App {
10381043
std::vector<Option *> get_options(const std::function<bool(Option *)> filter = {});
10391044

10401045
/// Get an option by name (noexcept non-const version)
1041-
Option *get_option_no_throw(std::string option_name) noexcept;
1046+
CLI11_NODISCARD Option *get_option_no_throw(std::string option_name) noexcept;
10421047

10431048
/// Get an option by name (noexcept const version)
10441049
CLI11_NODISCARD const Option *get_option_no_throw(std::string option_name) const noexcept;

include/CLI/impl/App_inl.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,10 @@ CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand(std::string subcom) const
457457
return subc;
458458
}
459459

460+
CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand_no_throw(std::string subcom) const noexcept {
461+
return _find_subcommand(subcom, false, false);
462+
}
463+
460464
CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand(int index) const {
461465
if(index >= 0) {
462466
auto uindex = static_cast<unsigned>(index);
@@ -796,7 +800,7 @@ CLI11_INLINE std::vector<Option *> App::get_options(const std::function<bool(Opt
796800
return options;
797801
}
798802

799-
CLI11_INLINE Option *App::get_option_no_throw(std::string option_name) noexcept {
803+
CLI11_NODISCARD CLI11_INLINE Option *App::get_option_no_throw(std::string option_name) noexcept {
800804
for(Option_p &opt : options_) {
801805
if(opt->check_name(option_name)) {
802806
return opt.get();
@@ -1441,12 +1445,8 @@ CLI11_INLINE void App::_parse_config(const std::vector<ConfigItem> &args) {
14411445
CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t level) {
14421446

14431447
if(level < item.parents.size()) {
1444-
try {
1445-
auto *subcom = get_subcommand(item.parents.at(level));
1446-
return subcom->_parse_single_config(item, level + 1);
1447-
} catch(const OptionNotFound &) {
1448-
return false;
1449-
}
1448+
auto *subcom = get_subcommand_no_throw(item.parents.at(level));
1449+
return (subcom != nullptr) ? subcom->_parse_single_config(item, level + 1) : false;
14501450
}
14511451
// check for section open
14521452
if(item.name == "++") {

tests/SubcommandTest.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ TEST_CASE_METHOD(TApp, "BasicSubcommands", "[subcom]") {
1616

1717
CHECK(app.get_subcommand(sub1) == sub1);
1818
CHECK(app.get_subcommand("sub1") == sub1);
19+
CHECK(app.get_subcommand_no_throw("sub1") == sub1);
1920
CHECK_THROWS_AS(app.get_subcommand("sub3"), CLI::OptionNotFound);
20-
21+
CHECK_NOTHROW(app.get_subcommand_no_throw("sub3"));
22+
CHECK(app.get_subcommand_no_throw("sub3") == nullptr);
2123
run();
2224
CHECK(app.get_subcommands().empty());
2325

@@ -90,7 +92,7 @@ TEST_CASE_METHOD(TApp, "MultiSubFallthrough", "[subcom]") {
9092
CHECK(!sub2->parsed());
9193
CHECK(0u == sub2->count());
9294

93-
CHECK_THROWS_AS(app.got_subcommand("sub3"), CLI::OptionNotFound);
95+
CHECK(!app.got_subcommand("sub3"));
9496
}
9597

9698
TEST_CASE_METHOD(TApp, "CrazyNameSubcommand", "[subcom]") {

0 commit comments

Comments
 (0)