diff --git a/cpp-argon-demo b/cpp-argon-demo index 9645827d..90dd0c13 160000 --- a/cpp-argon-demo +++ b/cpp-argon-demo @@ -1 +1 @@ -Subproject commit 9645827da9a9a01dde6ace31bcce536cf7127324 +Subproject commit 90dd0c13cf27ceef49b7288ba4f83879f3974c9c diff --git a/docs/tutorial.md b/docs/tutorial.md index c33532e0..046ae6b8 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1405,9 +1405,9 @@ Now all the values, that caused an exception for the `parse_args` example, are c You can retrieve the argument's value(s) with: ```cpp -(const) value_type value = parser.value("argument_name"); // (1) -(const) value_type value = parser.value_or("argument_name", fallback_value); // (2) -(const) std::vector values = parser.values("argument_name"); // (3) +/*const*/ value_type value = parser.value("argument_name"); // (1) +/*const*/ value_type value = parser.value_or("argument_name", fallback_value); // (2) +const std::vector& values = parser.values("argument_name"); // (3) ``` 1. Returns the given argument's value. diff --git a/include/argon/argument.hpp b/include/argon/argument.hpp index 55dd2711..97a23f6c 100644 --- a/include/argon/argument.hpp +++ b/include/argon/argument.hpp @@ -13,7 +13,6 @@ #include "argon/nargs/range.hpp" #include "argon/types.hpp" #include "argon/util/concepts.hpp" -#include "argon/util/ranges.hpp" #ifdef AP_TESTING @@ -55,7 +54,7 @@ enum class argument_type : bool { positional, optional }; * @tparam T The value type accepted by the argument (defaults to std::string). */ template -class argument : public detail::argument_base { +class argument : public detail::typed_argument_base { public: using value_type = T; ///< The argument's value type alias. using count_type = nargs::count_type; ///< The argument's count type alias. @@ -344,7 +343,7 @@ class argument : public detail::argument_base { requires(not util::c_is_none and std::equality_comparable) { for (const auto& value : values) - this->_default_values.emplace_back(std::make_any(value)); + this->_default_values.emplace_back(value); this->_required = false; return *this; } @@ -372,7 +371,7 @@ class argument : public detail::argument_base { argument& default_values(const std::convertible_to auto&... values) noexcept requires(not util::c_is_none) { - (this->_default_values.emplace_back(std::make_any(values)), ...); + (this->_default_values.emplace_back(values), ...); this->_required = false; return *this; } @@ -389,7 +388,7 @@ class argument : public detail::argument_base { requires(not util::c_is_none and type == argument_type::optional) { for (const auto& value : values) - this->_implicit_values.emplace_back(std::make_any(value)); + this->_implicit_values.emplace_back(value); return *this; } @@ -414,7 +413,7 @@ class argument : public detail::argument_base { argument& implicit_values(const std::convertible_to auto&... values) noexcept requires(not util::c_is_none and type == argument_type::optional) { - (this->_implicit_values.emplace_back(std::make_any(values)), ...); + (this->_implicit_values.emplace_back(values), ...); return *this; } @@ -474,15 +473,10 @@ class argument : public detail::argument_base { if (not this->_choices.empty()) bld.add_range_param("choices", this->_choices); if (not this->_default_values.empty()) - bld.add_range_param( - "default value(s)", util::any_range_cast_view(this->_default_values) - ); + bld.add_range_param("default value(s)", this->_default_values); if constexpr (type == argument_type::optional) { if (not this->_implicit_values.empty()) - bld.add_range_param( - "implicit value(s)", - util::any_range_cast_view(this->_implicit_values) - ); + bld.add_range_param("implicit value(s)", this->_implicit_values); } } @@ -557,7 +551,7 @@ class argument : public detail::argument_base { * @note If multiple values are available, the first one is returned. * @throws std::logic_error if no values are available. */ - [[nodiscard]] const std::any& value() const override { + [[nodiscard]] detail::arg_return_type value() const override { if (this->has_parsed_values()) return this->_values.front(); @@ -570,13 +564,13 @@ class argument : public detail::argument_base { } /// @return Reference to the vector of parsed values for the argument. - [[nodiscard]] const std::vector& values() const override { + [[nodiscard]] const std::vector& values() const override { return this->_values_impl(); } /// @return Reference to the vector of parsed values for the argument. /// @note For none-type arguments, the method always returns an empty vector. - [[nodiscard]] const std::vector& _values_impl() const noexcept + [[nodiscard]] const std::vector& _values_impl() const noexcept requires(util::c_is_none) { return this->_values; @@ -587,7 +581,7 @@ class argument : public detail::argument_base { * @note If no parsed values are available, the method attempts to return the predefined values (default/implicit). * @note The method is enabled only if `value_type` is not `none_type`. */ - [[nodiscard]] const std::vector& _values_impl() const noexcept + [[nodiscard]] const std::vector& _values_impl() const noexcept requires(not util::c_is_none) { if (this->has_parsed_values()) @@ -631,7 +625,7 @@ class argument : public detail::argument_base { * @note - For positional arguments, the default value list is returned. * @note - For optional arguments, if the argument has been used, the implicit value list is returned, otherwise the default value list is returned. */ - [[nodiscard]] const std::vector& _predefined_values() const + [[nodiscard]] const std::vector& _predefined_values() const requires(not util::c_is_none) { if constexpr (type == argument_type::optional) { @@ -726,9 +720,9 @@ class argument : public detail::argument_base { const argon::detail::argument_name _name; ///< The argument's name. std::optional _help_msg; ///< The argument's help message. nargs::range _nargs_range; ///< The argument's nargs range attribute value. - [[no_unique_address]] value_arg_specific_type> + [[no_unique_address]] value_arg_specific_type> _default_values; ///< The argument's default value list. - [[no_unique_address]] value_arg_specific_type>> + [[no_unique_address]] value_arg_specific_type>> _implicit_values; ///< The optional argument's implicit value list. [[no_unique_address]] value_arg_specific_type> _choices; ///< The argument's valid choices collection. @@ -748,7 +742,7 @@ class argument : public detail::argument_base { // parsing result [[no_unique_address]] optional_specific_type _count; ///< The argument's value count. - std::vector _values; ///< The argument's parsed values. + std::vector _values; ///< The argument's parsed values. // default attribute values static constexpr bool _default_required = (type == argument_type::positional); diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 60baf404..19e227d0 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -13,7 +13,6 @@ #include "argon/argument_group.hpp" #include "argon/detail/argument_token.hpp" #include "argon/types.hpp" -#include "argon/util/ranges.hpp" #include #include @@ -167,8 +166,8 @@ void add_default_argument(const default_argument, argument_parser&) noexcept; * .try_parse_args(argc, argv); * * // Access parsed argument values - * const std::string input_file = parser.value("input"); - * const std::string output_file = parser.value("output"); + * const std::string& input_file = parser.value("input"); + * const std::string& output_file = parser.value("output"); * * // Application logic here * std::cout << "Copying from " << input_file << " to " << output_file << std::endl; @@ -782,15 +781,14 @@ class argument_parser { * @throws argon::lookup_failure, argon::type_error */ template - [[nodiscard]] T value(std::string_view arg_name) const { + [[nodiscard]] detail::arg_return_type value(std::string_view arg_name) const { const auto arg = this->_get_argument(arg_name); - try { - return std::any_cast(arg->value()); - } - catch (const std::bad_any_cast&) { + const auto* typed_arg = dynamic_cast*>(arg.get()); + if (not typed_arg) throw type_error::invalid_value_type(arg->name()); - } + + return typed_arg->value(); } /** @@ -806,17 +804,18 @@ class argument_parser { [[nodiscard]] T value_or(std::string_view arg_name, U&& fallback_value) const { const auto arg = this->_get_argument(arg_name); + const auto* typed_arg = dynamic_cast*>(arg.get()); + if (not typed_arg) + throw type_error::invalid_value_type(arg->name()); + try { - return std::any_cast(arg->value()); + return typed_arg->value(); } catch (const std::logic_error&) { // positional: no value parsed // optional: no value parsed + no predefined value return T{std::forward(fallback_value)}; } - catch (const std::bad_any_cast&) { - throw type_error::invalid_value_type(arg->name()); - } } /** @@ -825,22 +824,16 @@ class argument_parser { * @param arg_name The name of the argument. * @return The values of the argument as a vector. * @throws argon::lookup_failure, argon::type_error - * @todo Use std::ranges::to after transition to C++23 for range casting */ template - [[nodiscard]] std::vector values(std::string_view arg_name) const { + [[nodiscard]] const std::vector& values(std::string_view arg_name) const { const auto arg = this->_get_argument(arg_name); - try { - std::vector values; - std::ranges::copy( - util::any_range_cast_view(arg->values()), std::back_inserter(values) - ); - return values; - } - catch (const std::bad_any_cast&) { + const auto* typed_arg = dynamic_cast*>(arg.get()); + if (not typed_arg) throw type_error::invalid_value_type(arg->name()); - } + + return typed_arg->values(); } /** diff --git a/include/argon/detail/argument_base.hpp b/include/argon/detail/argument_base.hpp index 80042abf..acbfa51f 100644 --- a/include/argon/detail/argument_base.hpp +++ b/include/argon/detail/argument_base.hpp @@ -12,8 +12,8 @@ #include "argon/detail/argument_name.hpp" #include "argon/detail/help_builder.hpp" -#include #include +#include #include namespace argon { @@ -87,12 +87,24 @@ class argument_base { /// @return The ordering relationship of argument range. virtual std::weak_ordering nvalues_ordering() const noexcept = 0; +}; + +/// @brief Returns by value if it is a *small* and trivially copyable. Otherwise, returns by const reference. +/// @note This trait was introduced to avoid problems with `std::vector` reference proxy objects. +/// @todo Move to the `argon::traits` namespace +template +using arg_return_type = + std::conditional_t and sizeof(T) <= sizeof(void*), T, const T&>; +/// @brief Strongly-typed intermediate argument interface +template +class typed_argument_base : public argument_base { +public: /// @return Reference to the stored value of the argument. - virtual const std::any& value() const = 0; + virtual arg_return_type value() const = 0; /// @return Reference to the vector of parsed values of the argument. - virtual const std::vector& values() const = 0; + virtual const std::vector& values() const = 0; }; } // namespace detail diff --git a/include/argon/exceptions.hpp b/include/argon/exceptions.hpp index 0f27f63c..77c34140 100644 --- a/include/argon/exceptions.hpp +++ b/include/argon/exceptions.hpp @@ -85,7 +85,7 @@ struct type_error : public argument_parser_exception { return type_error(std::format( "Invalid value type specified for argument [{}] = {}.", arg_name.str(), - util::get_demangled_type_name() + util::demangled_type_name() )); } }; diff --git a/include/argon/util/ranges.hpp b/include/argon/util/ranges.hpp deleted file mode 100644 index 867579fa..00000000 --- a/include/argon/util/ranges.hpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2023-2026 Jakub MusiaƂ -// This file is part of the CPP-ARGON project (https://github.com/SpectraL519/cpp-argon). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -/** - * @file argon/util/ranges.hpp - * @brief Provides common ranges utility functions. - */ - -#pragma once - -#include "argon/util/concepts.hpp" - -#include -#include - -namespace argon::util { - -/** - * @brief Casts a range of `std::any` to a range of type `T`. - * @tparam T The target type. - * @param range The input range of `std::any`. - * @return A view representing the casted range of type `T`. - * @throws std::bad_any_cast if any element in the input range cannot be cast to type `T`. - * @ingroup util - */ -template -constexpr auto any_range_cast_view(const c_range_of auto& range) { - return range | std::views::transform([](const std::any& value) -> T { - return std::any_cast(value); - }); -} - -} // namespace argon::util diff --git a/include/argon/util/typing.hpp b/include/argon/util/typing.hpp index 7da1edbc..134a4733 100644 --- a/include/argon/util/typing.hpp +++ b/include/argon/util/typing.hpp @@ -26,7 +26,7 @@ namespace argon::util { * @ingroup util */ template -constexpr std::string_view get_demangled_type_name() { +constexpr std::string_view demangled_type_name() { #if defined(__clang__) || defined(__GNUC__) constexpr std::string_view func_name = __PRETTY_FUNCTION__; constexpr std::string_view begin_key = "T = "; diff --git a/tests/include/argument_parser_test_fixture.hpp b/tests/include/argument_parser_test_fixture.hpp index 1dad91b3..5cc96a66 100644 --- a/tests/include/argument_parser_test_fixture.hpp +++ b/tests/include/argument_parser_test_fixture.hpp @@ -139,8 +139,8 @@ struct argument_parser_test_fixture { template void add_arguments(const std::size_t n_positional_args, std::size_t n_optional_args) { - add_positional_args(n_positional_args); - add_optional_args(n_optional_args, n_positional_args); + add_positional_args(n_positional_args); + add_optional_args(n_optional_args, n_positional_args); } [[nodiscard]] arg_token_vec_t init_arg_tokens( diff --git a/tests/include/argument_test_fixture.hpp b/tests/include/argument_test_fixture.hpp index 151bfea8..1c372fff 100644 --- a/tests/include/argument_test_fixture.hpp +++ b/tests/include/argument_test_fixture.hpp @@ -5,6 +5,7 @@ using argon::argument; using argon::argument_type; +using argon::detail::arg_return_type; using argon::detail::argument_name; using argon::detail::help_builder; using argon::util::as_string; @@ -43,12 +44,7 @@ struct argument_test_fixture { template void set_value_force(argument& arg, const T& value) const { - set_value_force(arg, as_string(value)); - } - - template - void set_value_force(argument& arg, const std::string& str_value) const { - arg._values.emplace_back(str_value); + arg._values.emplace_back(value); } template @@ -77,12 +73,12 @@ struct argument_test_fixture { } template - [[nodiscard]] const std::any& get_value(const argument& arg) const { + [[nodiscard]] arg_return_type get_value(const argument& arg) const { return arg.value(); } template - [[nodiscard]] const std::vector& get_values(const argument& arg) const { + [[nodiscard]] const std::vector& get_values(const argument& arg) const { return arg.values(); } diff --git a/tests/source/test_optional_argument.cpp b/tests/source/test_optional_argument.cpp index 3ed9f37c..12246e02 100644 --- a/tests/source/test_optional_argument.cpp +++ b/tests/source/test_optional_argument.cpp @@ -1,5 +1,6 @@ #include "argument_test_fixture.hpp" #include "doctest.h" +#include "utility.hpp" #include @@ -339,7 +340,7 @@ TEST_CASE_FIXTURE( sut.default_values(default_value); REQUIRE(has_value(sut)); - CHECK_EQ(std::any_cast(get_value(sut)), default_value); + CHECK_EQ(get_value(sut), default_value); } TEST_CASE_FIXTURE( @@ -362,7 +363,7 @@ TEST_CASE_FIXTURE( mark_used(sut); REQUIRE(has_value(sut)); - CHECK_EQ(std::any_cast(get_value(sut)), implicit_value); + CHECK_EQ(get_value(sut), implicit_value); } TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should return false by default") { @@ -455,7 +456,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); REQUIRE_FALSE(has_value(sut)); - CHECK_THROWS_AS(static_cast(get_value(sut)), std::logic_error); + CHECK_THROWS_AS(discard(get_value(sut)), std::logic_error); } TEST_CASE_FIXTURE( @@ -466,7 +467,7 @@ TEST_CASE_FIXTURE( sut.default_values(arbitrary_value); REQUIRE(has_value(sut)); - CHECK_EQ(std::any_cast(get_value(sut)), arbitrary_value); + CHECK_EQ(get_value(sut), arbitrary_value); } TEST_CASE_FIXTURE( @@ -479,7 +480,7 @@ TEST_CASE_FIXTURE( mark_used(sut); REQUIRE(has_value(sut)); - CHECK_EQ(std::any_cast(get_value(sut)), implicit_value); + CHECK_EQ(get_value(sut), implicit_value); } TEST_CASE_FIXTURE( @@ -491,7 +492,7 @@ TEST_CASE_FIXTURE( set_value(sut, arbitrary_value); REQUIRE(has_value(sut)); - CHECK_EQ(std::any_cast(get_value(sut)), arbitrary_value); + CHECK_EQ(get_value(sut), arbitrary_value); } TEST_CASE_FIXTURE( @@ -544,10 +545,7 @@ TEST_CASE_FIXTURE( for (const auto value : choices) REQUIRE_NOTHROW(set_value(sut, value)); - const auto stored_values = get_values(sut); - REQUIRE_EQ(stored_values.size(), choices.size()); - for (std::size_t i = 0; i < stored_values.size(); ++i) - REQUIRE_EQ(std::any_cast(stored_values[i]), choices[i]); + REQUIRE_EQ(get_values(sut), choices); CHECK_THROWS_WITH_AS( set_value(sut, arbitrary_value), @@ -574,7 +572,7 @@ TEST_CASE_FIXTURE( sut_value_type valid_value = 16; REQUIRE_NOTHROW(set_value(sut, valid_value)); - CHECK_EQ(std::any_cast(get_value(sut)), valid_value); + CHECK_EQ(get_value(sut), valid_value); } SUBCASE("transform action") { @@ -583,7 +581,7 @@ TEST_CASE_FIXTURE( set_value(sut, arbitrary_value); - CHECK_EQ(std::any_cast(get_value(sut)), double_action(arbitrary_value)); + CHECK_EQ(get_value(sut), double_action(arbitrary_value)); } SUBCASE("modify action") { @@ -595,7 +593,7 @@ TEST_CASE_FIXTURE( set_value(sut, test_value); double_action(test_value); - CHECK_EQ(std::any_cast(get_value(sut)), test_value); + CHECK_EQ(get_value(sut), test_value); } } diff --git a/tests/source/test_positional_argument.cpp b/tests/source/test_positional_argument.cpp index 451cf443..932da1c2 100644 --- a/tests/source/test_positional_argument.cpp +++ b/tests/source/test_positional_argument.cpp @@ -330,7 +330,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); REQUIRE_FALSE(has_value(sut)); - CHECK_THROWS_AS(static_cast(get_value(sut)), std::logic_error); + CHECK_THROWS_AS(discard(get_value(sut)), std::logic_error); } TEST_CASE_FIXTURE( @@ -340,7 +340,7 @@ TEST_CASE_FIXTURE( set_value(sut, valid_value); REQUIRE(has_value(sut)); - CHECK_EQ(std::any_cast(get_value(sut)), valid_value); + CHECK_EQ(get_value(sut), valid_value); } TEST_CASE_FIXTURE( @@ -352,7 +352,7 @@ TEST_CASE_FIXTURE( sut.default_values(default_value); REQUIRE(has_value(sut)); - CHECK_EQ(std::any_cast(get_value(sut)), default_value); + CHECK_EQ(get_value(sut), default_value); } TEST_CASE_FIXTURE( @@ -363,7 +363,7 @@ TEST_CASE_FIXTURE( set_value(sut, valid_value); REQUIRE(has_value(sut)); - CHECK_EQ(std::any_cast(get_value(sut)), valid_value); + CHECK_EQ(get_value(sut), valid_value); } TEST_CASE_FIXTURE( @@ -417,10 +417,7 @@ TEST_CASE_FIXTURE( for (const auto value : choices) REQUIRE_NOTHROW(set_value(sut, value)); - const auto stored_values = get_values(sut); - REQUIRE_EQ(stored_values.size(), choices.size()); - for (std::size_t i = 0; i < stored_values.size(); ++i) - REQUIRE_EQ(std::any_cast(stored_values[i]), choices[i]); + REQUIRE_EQ(get_values(sut), choices); CHECK_THROWS_WITH_AS( set_value(sut, valid_value), @@ -445,7 +442,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec sut_value_type valid_value = 16; REQUIRE_NOTHROW(set_value(sut, valid_value)); - CHECK_EQ(std::any_cast(get_value(sut)), valid_value); + CHECK_EQ(get_value(sut), valid_value); } SUBCASE("transform action") { @@ -454,7 +451,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec set_value(sut, valid_value); - CHECK_EQ(std::any_cast(get_value(sut)), double_action(valid_value)); + CHECK_EQ(get_value(sut), double_action(valid_value)); } SUBCASE("modify action") { @@ -466,7 +463,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec set_value(sut, test_value); double_action(test_value); - CHECK_EQ(std::any_cast(get_value(sut)), test_value); + CHECK_EQ(get_value(sut), test_value); } }