Intent
Increase code usability/readability of functions with many default parameters while maintaining as much compile time safety as possible.
Motivation
It is very common to create functions that have many default parameters that extend and customise their side-effects. A problem arises in function usability/readability when the parameter you want to overload is far down in the list but you want all the preceding parameters at their default values. This method also allows for optional parameters to be used as parameters in generating other optional parameters of the function (which cannot be done with default parameters).
Solution
template <typename... T>
void function_with_optional_params(int required_param, T... args) {
std::tuple<T...> tuple(std::forward<T>(args)...);
static_assert(std::tuple_size<std::tuple<T...>>::value <= 4, "To many arguments");
static_assert(xtd::all_true<
std::is_same<T, int>{} ||
std::is_same<T, std::string>{} ||
std::is_same<T, double>{} ||
std::is_same<T, float>{}
...>{}, "Unexpected argument types");
int first = xtd::get_first_default_func<int>(tuple, [](){ return -1; });
std::string second = xtd::get_first_default_value<std::string>(tuple, "-2");
double third = xtd::get_first_default_value<double>(tuple, -3.0);
float forth = xtd::get_first_default_func<float>(tuple, [](){ return -4.0; });
std::cout << required_param << "," << first << "," << second << "," << third << "," << forth << std::endl;
}
Helper Functions
namespace xtd {
namespace detail {
template <class T, std::size_t N, class... Args>
struct get_number_of_element_from_tuple_by_type_impl {
static constexpr auto value = N;
};
template <class T, std::size_t N, class... Args>
struct get_number_of_element_from_tuple_by_type_impl<T, N, T, Args...> {
static constexpr auto value = N;
};
template <class T, std::size_t N, class U, class... Args>
struct get_number_of_element_from_tuple_by_type_impl<T, N, U, Args...> {
static constexpr auto value = get_number_of_element_from_tuple_by_type_impl<T, N + 1, Args...>::value;
};
} // namespace detail
template <class T, class... Args>
T get_first(const std::tuple<Args...>& t) {
return std::get<detail::get_number_of_element_from_tuple_by_type_impl<T, 0, Args...>::value>(t);
}
template <class T, class... Args>
typename std::enable_if<detail::get_number_of_element_from_tuple_by_type_impl<T, 0, Args...>::value < std::tuple_size<std::tuple<Args...>>::value, T>::type
get_first_default_value(const std::tuple<Args...>& t, T d) {
return get_first<T>(t);
}
template <class T, class... Args>
typename std::enable_if<detail::get_number_of_element_from_tuple_by_type_impl<T, 0, Args...>::value == std::tuple_size<std::tuple<Args...>>::value, T>::type
get_first_default_value(const std::tuple<Args...>& t, T d) {
return d;
}
template <class T, class D, class... Args>
typename std::enable_if<detail::get_number_of_element_from_tuple_by_type_impl<T, 0, Args...>::value < std::tuple_size<std::tuple<Args...>>::value, T>::type
get_first_default_func(const std::tuple<Args...>& t, D d) {
return get_first<T>(t);
}
template <class T, class D, class... Args>
typename std::enable_if<detail::get_number_of_element_from_tuple_by_type_impl<T, 0, Args...>::value == std::tuple_size<std::tuple<Args...>>::value, T>::type
get_first_default_func(const std::tuple<Args...>& t, D d) {
return d();
}
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
}
Example Usage
// HTTP Request with optional callbacks and headers
void make_explicit_request(
client client,
method method,
const std::string& url,
callback_func on_callback,
headers custom_headers = headers(),
request_header_sender::send_func on_send = nullptr,
resolve_error_func on_resolve_error = nullptr,
connect_error_func on_connect_error = nullptr);
template <typename... T>
void make_request(
client client,
method method,
const std::string& url,
callback_func on_callback,
T... args) {
std::tuple<T...> tuple(std::forward<T>(args)...);
static_assert(std::tuple_size<std::tuple<T...>>::value <= 4, "To many arguments");
static_assert(xtd::all_true<
std::is_same<T, headers>{} ||
std::is_same<T, request_header_sender::send_func>{} ||
std::is_same<T, resolve_error_func>{} ||
std::is_same<T, connect_error_func>{}
...>{}, "Unexpected argument types");
headers custom_headers = xtd::get_first_default_func<headers>(tuple, [](){ return headers(); });
request_header_sender::send_func on_send = xtd::get_first_default_value<request_header_sender::send_func>(tuple, nullptr);
resolve_error_func on_resolve_error = xtd::get_first_default_value<resolve_error_func>(tuple, nullptr);
connect_error_func on_connect_error = xtd::get_first_default_value<connect_error_func>(tuple, nullptr);
make_explicit_request(client, method, url, on_callback, custom_headers, on_send, on_resolve_error, on_connect_error);
}
// Open a file for async read and/or write
void open(
loop loop,
const std::string& path,
int flags, int mode,
open_func on_open,
error_func on_open_error = nullptr,
complete_func on_close = nullptr,
error_func on_close_error = nullptr);
Known Issues
The code works as intended everywhere I use it, however these points need to be kept in mind:
- Requires that all optional parameters be of different types. I believe this is tolerable especially in light of type safe enums.
- Multiple optional parameters with same type will not cause compile errors. This could be mitigated with C++14
std::get<T>(tuple)
. - Modern IDEs will not be able to help with the optional parameter names.
It is very common to create functions that have many default parameters that extend and customise their side-effects.
Disagree. This is very uncommon and usually a sign of bad interface design. \$\endgroup\$