Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions include/sharg/detail/poison_config.hpp
Original file line number Diff line number Diff line change
@@ -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 <enrico.seiler AT fu-berlin.de>
* \brief Provides sharg::detail::poison_config.
*/

#pragma once

#include <sharg/config.hpp>

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 <typename validator_t = poison_config_size_comp_validator>
concept poison_config_valid =
(sizeof(poison_config) == sizeof(config<validator_t>))
&& std::same_as<decltype(poison_config{}.short_id), decltype(config<validator_t>{}.short_id)>
&& std::same_as<decltype(poison_config{}.long_id), decltype(config<validator_t>{}.long_id)>
&& std::same_as<decltype(poison_config{}.description), decltype(config<validator_t>{}.description)>
&& std::same_as<decltype(poison_config{}.default_message), decltype(config<validator_t>{}.default_message)>
&& std::same_as<decltype(poison_config{}.advanced), decltype(config<validator_t>{}.advanced)>
&& std::same_as<decltype(poison_config{}.hidden), decltype(config<validator_t>{}.hidden)>
&& std::same_as<decltype(poison_config{}.required), decltype(config<validator_t>{}.required)>
&& requires (config<validator_t> 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<validator_t>{.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<validator_t>(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 <typename option_type>
* void add_option(option_type)
* {
* if constexpr (std::is_same_v<option_type, int>)
* {
* return;
* }
* else
* {
* static_assert(false, "Should never happen"); // invalid before CWG2518
* static_assert(dependent_false_v<option_type>, "Should never happen"); // valid
* }
* }
* ```
*/
template <typename>
inline constexpr bool dependent_false_v = false;

} // namespace sharg::detail
29 changes: 29 additions & 0 deletions include/sharg/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <sharg/detail/format_man.hpp>
#include <sharg/detail/format_parse.hpp>
#include <sharg/detail/format_tdl.hpp>
#include <sharg/detail/poison_config.hpp>
#include <sharg/detail/version_check.hpp>

namespace sharg
Expand Down Expand Up @@ -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 <typename option_type>
void add_option(option_type &, detail::poison_config const &)
{
static_assert(detail::dependent_false_v<option_type>, "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.
Expand Down Expand Up @@ -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 <typename option_type> // 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<option_type>, "Forgot sharg::config?");
}
//!\endcond

/*!\brief Adds a positional option to the sharg::parser.
*
* \tparam option_type Must have a formatted input function (stream >> value).
Expand Down Expand Up @@ -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 <typename option_type>
void add_positional_option(option_type &, detail::poison_config const &)
{
static_assert(detail::dependent_false_v<option_type>, "Forgot sharg::config?");
}
//!\endcond

//!\}

/*!\brief Initiates the actual command line parsing.
Expand Down