Skip to content

Commit

Permalink
InputSpec: optional parameters have empty default
Browse files Browse the repository at this point in the history
There is no reason to allow users to specify a default value other than
the empty state. If another value is desired, the parameter should not
be a std::optional parameter.
  • Loading branch information
sebproell committed Feb 27, 2025
1 parent b991239 commit ee8e05a
Show file tree
Hide file tree
Showing 30 changed files with 381 additions and 521 deletions.
1 change: 0 additions & 1 deletion src/core/io/src/4C_io_input_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,6 @@ namespace Core::IO
{
.description = "Path to files that should be included into this file. "
"The paths can be either absolute or relative to the file.",
.default_value = std::nullopt,
});
}

Expand Down
145 changes: 103 additions & 42 deletions src/core/io/src/4C_io_input_spec_builders.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,26 @@ namespace Core::IO
*/
using ParameterCallback = std::function<void(InputParameterContainer&)>;

/**
* A tag type to indicate that a parameter cannot take default values.
*/
struct NoDefault
{
};

/**
* The type used for the default value of a parameter. If the parameter is optional, the user
* cannot specify a default value, so this type becomes NoDefault. Otherwise, the default value
* can be either not set, resulting in the std::monostate, or set to a value of the parameter
* type.
*/
template <typename T>
using DefaultType =
std::conditional_t<OptionalType<T>, NoDefault, std::variant<std::monostate, T>>;

//! Additional parameters for a parameter().
template <typename T>
struct ParameterData
struct ParameterDataIn
{
using StoredType = T;
/**
Expand All @@ -560,7 +577,7 @@ namespace Core::IO
* The default value of the parameter. If this field is set, the parameter does not need to be
* entered in the input. If the parameter is not entered, this default value is used.
*/
std::variant<std::monostate, StoredType> default_value{};
DefaultType<T> default_value{};

/**
* An optional callback that is called after the value has been parsed. This can be used to
Expand All @@ -571,13 +588,13 @@ namespace Core::IO

template <typename T>
requires(rank<T>() == 1)
struct ParameterData<T>
struct ParameterDataIn<T>
{
using StoredType = T;

std::string description{};

std::variant<std::monostate, StoredType> default_value{};
DefaultType<T> default_value{};

ParameterCallback on_parse_callback{nullptr};

Expand All @@ -590,13 +607,13 @@ namespace Core::IO

template <typename T>
requires(rank<T>() > 1)
struct ParameterData<T>
struct ParameterDataIn<T>
{
using StoredType = T;

std::string description{};

std::variant<std::monostate, StoredType> default_value{};
DefaultType<T> default_value{};

ParameterCallback on_parse_callback{nullptr};

Expand Down Expand Up @@ -644,6 +661,32 @@ namespace Core::IO

namespace Internal
{
template <typename T>
struct ParameterData
{
using StoredType = T;

std::string description{};

std::variant<std::monostate, StoredType> default_value{};

ParameterCallback on_parse_callback{nullptr};

std::array<Size, rank<T>()> size{};
};

template <typename T>
struct SelectionData
{
using StoredType = T;

std::string description{};

std::variant<std::monostate, StoredType> default_value{};

ParameterCallback on_parse_callback{nullptr};
};

template <SupportedType T>
struct ParameterSpec
{
Expand Down Expand Up @@ -673,7 +716,7 @@ namespace Core::IO
using InputType =
std::conditional_t<OptionalType<T>, std::optional<std::string>, std::string>;
using ChoiceMap = std::map<InputType, StoredType>;
ParameterData<T> data;
SelectionData<T> data;
ChoiceMap choices;
//! The string representation of the choices.
std::string choices_string;
Expand Down Expand Up @@ -767,7 +810,7 @@ namespace Core::IO
//! Note that the type can be anything since we never read or write values of this type.
template <typename T>
[[nodiscard]] InputSpec selection_internal(std::string name,
std::map<std::string, RemoveOptional<T>> choices, ParameterData<T> data = {});
std::map<std::string, RemoveOptional<T>> choices, ParameterDataIn<T> data = {});


struct SizeChecker
Expand Down Expand Up @@ -866,7 +909,7 @@ namespace Core::IO
* @relatedalso InputSpec
*/
template <SupportedType T>
[[nodiscard]] InputSpec parameter(std::string name, ParameterData<T>&& data = {});
[[nodiscard]] InputSpec parameter(std::string name, ParameterDataIn<T>&& data = {});

/**
* Create a callback that returns the value of the parameter with the given @p name. Such a
Expand Down Expand Up @@ -909,7 +952,7 @@ namespace Core::IO
template <typename T>
requires(!std::same_as<T, std::string>)
[[nodiscard]] InputSpec selection(std::string name,
std::map<std::string, RemoveOptional<T>> choices, ParameterData<T> data = {});
std::map<std::string, RemoveOptional<T>> choices, ParameterDataIn<T> data = {});


/**
Expand All @@ -923,7 +966,7 @@ namespace Core::IO
*/
template <std::same_as<std::string> T>
[[nodiscard]] InputSpec selection(
std::string name, std::vector<std::string> choices, ParameterData<T> data = {});
std::string name, std::vector<std::string> choices, ParameterDataIn<T> data = {});

/**
* A group of InputSpecs. This groups one or more InputSpecs under a name. A group can be
Expand Down Expand Up @@ -1189,13 +1232,7 @@ void Core::IO::InputSpecBuilders::Internal::ParameterSpec<T>::parse(
InputParameterContainer& container;
};

if constexpr (rank<T>() == 1)
{
std::size_t size_info = std::visit(SizeVisitor{container}, data.size);
auto parsed = parser.read<T>(size_info);
container.add(name, parsed);
}
else if constexpr (rank<T>() > 1)
if constexpr (rank<T>() > 0)
{
std::array<std::size_t, rank<T>()> size_info;
for (std::size_t i = 0; i < rank<T>(); ++i)
Expand Down Expand Up @@ -1286,14 +1323,9 @@ void Core::IO::InputSpecBuilders::Internal::ParameterSpec<T>::emit_metadata(
};

std::array<std::size_t, rank<T>()> size_info;
if constexpr (rank<T>() == 1)
size_info[0] = std::visit(DynamicSizeVisitor{}, data.size);
else
for (std::size_t i = 0; i < rank<T>(); ++i)
{
for (std::size_t i = 0; i < rank<T>(); ++i)
{
size_info[i] = std::visit(DynamicSizeVisitor{}, data.size[i]);
}
size_info[i] = std::visit(DynamicSizeVisitor{}, data.size[i]);
}
IO::Internal::emit_type_as_yaml<StoredType>(node, size_info);
}
Expand Down Expand Up @@ -1367,14 +1399,9 @@ bool Core::IO::InputSpecBuilders::Internal::ParameterSpec<T>::has_correct_size(
};

std::array<std::size_t, rank<T>()> size_info;
if constexpr (rank<T>() == 1)
size_info[0] = std::visit(SizeVisitor{container}, data.size);
else
for (std::size_t i = 0; i < rank<T>(); ++i)
{
for (std::size_t i = 0; i < rank<T>(); ++i)
{
size_info[i] = std::visit(SizeVisitor{container}, data.size[i]);
}
size_info[i] = std::visit(SizeVisitor{container}, data.size[i]);
}
SizeChecker size_checker;
return size_checker(val, size_info.data());
Expand Down Expand Up @@ -1596,22 +1623,43 @@ auto Core::IO::InputSpecBuilders::from_parameter(const std::string& name)

template <Core::IO::SupportedType T>
Core::IO::InputSpec Core::IO::InputSpecBuilders::parameter(
std::string name, ParameterData<T>&& data)
std::string name, ParameterDataIn<T>&& data)
{
return IO::Internal::make_spec(Internal::ParameterSpec<T>{.name = name, .data = data},
Internal::ParameterData<T> internal_data;
internal_data.description = data.description;
if constexpr (OptionalType<T>)
{
// An optional<T> implies a default_value corresponding to the empty state.
internal_data.default_value = std::nullopt;
}
else
{
internal_data.default_value = data.default_value;
}
if constexpr (rank<T>() == 1)
{
internal_data.size[0] = data.size;
}
else if constexpr (rank<T>() > 1)
{
internal_data.size = data.size;
}
internal_data.on_parse_callback = data.on_parse_callback;

return IO::Internal::make_spec(Internal::ParameterSpec<T>{.name = name, .data = internal_data},
{
.name = name,
.description = data.description,
.required = !(data.default_value.index() == 1),
.has_default_value = data.default_value.index() == 1,
.required = !(internal_data.default_value.index() == 1),
.has_default_value = internal_data.default_value.index() == 1,
.n_specs = 1,
});
}


template <typename T>
Core::IO::InputSpec Core::IO::InputSpecBuilders::Internal::selection_internal(
std::string name, std::map<std::string, RemoveOptional<T>> choices, ParameterData<T> data)
std::string name, std::map<std::string, RemoveOptional<T>> choices, ParameterDataIn<T> data)
{
FOUR_C_ASSERT_ALWAYS(!choices.empty(), "Selection must have at least one choice.");

Expand All @@ -1631,12 +1679,25 @@ Core::IO::InputSpec Core::IO::InputSpecBuilders::Internal::selection_internal(
choices_string += "|none";
}

const bool has_default_value = data.default_value.index() == 1;
SelectionData<T> internal_data;
internal_data.description = data.description;
if constexpr (OptionalType<T>)
{
// An optional<T> implies a default_value corresponding to the empty state.
internal_data.default_value = std::nullopt;
}
else
{
internal_data.default_value = data.default_value;
}
internal_data.on_parse_callback = data.on_parse_callback;

const bool has_default_value = internal_data.default_value.index() == 1;

// Check that we have a default value that is in the choices.
if (has_default_value)
{
const auto& default_value = std::get<1>(data.default_value);
const auto& default_value = std::get<1>(internal_data.default_value);
auto default_value_it = std::find_if(modified_choices.begin(), modified_choices.end(),
[&](const auto& choice) { return choice.second == default_value; });

Expand All @@ -1652,7 +1713,7 @@ Core::IO::InputSpec Core::IO::InputSpecBuilders::Internal::selection_internal(
return IO::Internal::make_spec(
Internal::SelectionSpec<T>{
.name = name,
.data = data,
.data = internal_data,
.choices = modified_choices,
.choices_string = choices_string,
},
Expand All @@ -1669,15 +1730,15 @@ Core::IO::InputSpec Core::IO::InputSpecBuilders::Internal::selection_internal(
template <typename T>
requires(!std::same_as<T, std::string>)
Core::IO::InputSpec Core::IO::InputSpecBuilders::selection(
std::string name, std::map<std::string, RemoveOptional<T>> choices, ParameterData<T> data)
std::string name, std::map<std::string, RemoveOptional<T>> choices, ParameterDataIn<T> data)
{
return Internal::selection_internal(name, choices, data);
}


template <std::same_as<std::string> T>
Core::IO::InputSpec Core::IO::InputSpecBuilders::selection(
std::string name, std::vector<std::string> choices, ParameterData<T> data)
std::string name, std::vector<std::string> choices, ParameterDataIn<T> data)
{
std::map<std::string, std::string> choices_with_strings;
for (const auto& choice : choices)
Expand Down
Loading

0 comments on commit ee8e05a

Please sign in to comment.