diff --git a/include/sharg/detail/poison_config.hpp b/include/sharg/detail/poison_config.hpp new file mode 100644 index 00000000..d728b1ea --- /dev/null +++ b/include/sharg/detail/poison_config.hpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2006-2024, Knut Reinert & Freie Universität Berlin +// SPDX-FileCopyrightText: 2016-2024, Knut Reinert & MPI für molekulare Genetik +// SPDX-License-Identifier: BSD-3-Clause + +/*!\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::config and sharg::detail::poison_config have the same member types (except validator). + * * 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. + * The latter two ensure that the order of the members is the same. + */ +template +concept poison_config_valid = + (sizeof(poison_config) == sizeof(config)) + && std::same_as{}.short_id)> + && std::same_as{}.long_id)> + && std::same_as{}.description)> + && std::same_as{}.default_message)> + && std::same_as{}.advanced)> + && std::same_as{}.hidden)> + && std::same_as{}.required)> + && 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!"); + +/*!\brief This is a workaround for compilers that do not implement CWG2518 (GCC 11, GCC 12). + * \ingroup parser + * \sa https://en.cppreference.com/w/cpp/language/if#Constexpr_if + * \sa https://cplusplus.github.io/CWG/issues/2518.html + * \details + * Before CWG2518, a (discarded) statement couldn't be false in every case, e.g. + * ```cpp + * template + * void add_option(option_type) + * { + * if constexpr (std::is_same_v) + * { + * return; + * } + * else + * { + * static_assert(false, "Should never happen"); // invalid before CWG2518 + * static_assert(dependent_false_v, "Should never happen"); // valid + * } + * } + * ``` + */ +template +inline constexpr bool dependent_false_v = false; + +} // namespace sharg::detail diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index 26169797..36264e8a 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace sharg @@ -254,6 +255,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(detail::dependent_false_v, "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. @@ -283,6 +293,15 @@ 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. + void add_flag(option_type &, detail::poison_config const &) + { + static_assert(detail::dependent_false_v, "Forgot sharg::config?"); + } + //!\endcond + /*!\brief Adds a positional option to the sharg::parser. * * \tparam option_type Must have a formatted input function (stream >> value). @@ -324,6 +343,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(detail::dependent_false_v, "Forgot sharg::config?"); + } + //!\endcond + //!\} /*!\brief Initiates the actual command line parsing.