diff --git a/include/sharg/detail/poison_config.hpp b/include/sharg/detail/poison_config.hpp new file mode 100644 index 00000000..aa60ae94 --- /dev/null +++ b/include/sharg/detail/poison_config.hpp @@ -0,0 +1,92 @@ +// -------------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2023, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2023, Knut Reinert & MPI für molekulare Genetik +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/sharg-parser/blob/main/LICENSE.md +// -------------------------------------------------------------------------------------------------------- + +/*!\file + * \author Enrico Seiler + * \brief Provides sharg::detail::poison_config. + */ + +#pragma once + +#include + +namespace sharg::detail +{ + +/*!\brief This struct is used to prevent the user from calling sharg::parser::add_option, sharg::parser::add_flag, + * and sharg::parser::add_positional_option without a sharg::config. + * \ingroup parser + * \details + * This prevents (and produces a legible error message) for calls like: + * ``` + * parser.add_option(value, {.short_id = 'i', .long_id = "int", .description = "Desc."}); + * ``` + * instead of + * ``` + * parser.add_option(value, sharg::config{.short_id = 'i', .long_id = "int", .description = "Desc."}); + * ``` + */ +struct poison_config +{ + char short_id{'\0'}; //!< Same as sharg::config::short_id. + std::string long_id{}; //!< Same as sharg::config::long_id. + std::string description{}; //!< Same as sharg::config::description. + std::string default_message{}; //!< Same as sharg::config::default_message. + bool advanced{false}; //!< Same as sharg::config::advanced. + bool hidden{false}; //!< Same as sharg::config::hidden. + bool required{false}; //!< Same as sharg::config::required. + std::any validator{}; //!< Prevents CTAD inside a function call, which would cause a compiler error. +}; + +/*!\brief A validator used for comparing the size of sharg::config and sharg::poison_config. + * \ingroup parser + * \details + * The `sizeof(std::any)` is typically `16`, while `sizeof(sharg::detail::default_validator)` is `1`. + * `poison_config_size_comp_validator` provides a validator whose size is the same as the size of `std::any`. + */ +struct poison_config_size_comp_validator : public detail::default_validator +{ + std::any validator{}; //!< A member such that the sizes of sharg::config and sharg::poison_config are the same. +}; + +/*!\concept sharg::detail::poison_config_valid + * \ingroup parser + * \brief Concept that checks that sharg::poison_config has the same members as sharg::config. + * \tparam validator_t The validator to use. Defaults to sharg::detail::poison_config_size_comp_validator. + * \details + * * Sizes of sharg::config and sharg::detail::poison_config are the same. + * * sharg::detail::poison_config can be constructed with designated initializers from sharg::config's members. + * * sharg::config can be constructed with designated initializers from sharg::detail::poison_config's members. + */ +template +concept poison_config_valid = (sizeof(poison_config) == sizeof(config)) + && requires (config cfg, poison_config poison_cfg) { + { + poison_config{.short_id = cfg.short_id, + .long_id = cfg.long_id, + .description = cfg.description, + .default_message = cfg.default_message, + .advanced = cfg.advanced, + .hidden = cfg.hidden, + .required = cfg.required, + .validator = cfg.validator} + }; + { + config{.short_id = poison_cfg.short_id, + .long_id = poison_cfg.long_id, + .description = poison_cfg.description, + .default_message = poison_cfg.default_message, + .advanced = poison_cfg.advanced, + .hidden = poison_cfg.hidden, + .required = poison_cfg.required, + .validator = std::any_cast(poison_cfg.validator)} + }; + }; + +static_assert(poison_config_valid<>, "sharg::detail::poison_config must have the same members as sharg::config!"); + +} // namespace sharg::detail diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index 7e22e285..3f5f5b30 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace sharg @@ -257,6 +258,15 @@ class parser format); } + //!\cond DEV + //!\brief A poison overload that catches calls to add_option without explicitly passing a sharg::config. + template + void add_option(option_type &, detail::poison_config const &) + { + static_assert(false, "Forgot sharg::config?"); + } + //!\endcond + /*!\brief Adds a flag to the sharg::parser. * * \param[in, out] value The variable which shows if the flag is turned off (default) or on. @@ -286,6 +296,16 @@ class parser format); } + //!\cond DEV + //!\brief A poison overload that catches calls to add_flag without explicitly passing a sharg::config. + template // Template needed to prevent instantiation of this function if unused. + requires std::same_as + void add_flag(option_type &, detail::poison_config const &) + { + static_assert(false, "Forgot sharg::config?"); + } + //!\endcond + /*!\brief Adds a positional option to the sharg::parser. * * \tparam option_type Must have a formatted input function (stream >> value). @@ -327,6 +347,16 @@ class parser }, format); } + + //!\cond DEV + //!\brief A poison overload that catches calls to add_positional_option without explicitly passing a sharg::config. + template + void add_positional_option(option_type &, detail::poison_config const &) + { + static_assert(false, "Forgot sharg::config?"); + } + //!\endcond + //!\} /*!\brief Initiates the actual command line parsing.