Info
-
Did you know about (rejected) proposal for homogeneous variadic function parameters?
Example
#include <concepts>
// auto fn(int...) -> void; // error
auto fn(int, ...) -> void ; // ok
auto fn(std::same_as<int> auto...) -> void; // ok
Puzzle
-
Can you implement
safe_call
which will call given function with variadic type-safe input parameters from propagted from ...?- Max number of parameters is 3
- fn is expected to be called with types coresponding to input parameters (not just strings, for example)
auto safe_call(auto fn, auto fmt, ...); // TODO
int main() {
{
std::stringstream str{};
safe_call([&](auto... args) { ((str << args), ...); }, "");
assert(std::string{""} == str.str());
}
{
std::stringstream str{};
safe_call([&](auto... args) { ((str << args), ...); }, "id", 4, 2.);
assert(std::string{"42"} == str.str());
}
{
std::stringstream str{};
safe_call([&](auto... args) { ((str << args), ...); }, "di", 3.2, 1);
assert(std::string{"3.21"} == str.str());
}
{
std::stringstream str{};
safe_call([&](auto... args) { ((str << args), ...); }, "idi", 1, 2.3, 4);
assert(std::string{"12.34"} == str.str());
}
}
Solutions
void assert_fmt(char fmt, char required_fmt) {
if (fmt != required_fmt) {
throw std::runtime_error(std::string("fmt must be '") + required_fmt + "'");
}
}
template<class T>
requires std::same_as<T, int>
void assert_fmt(char fmt) {
assert_fmt(fmt, 'i');
}
template<class T>
requires std::same_as<T, double>
void assert_fmt(char fmt) {
assert_fmt(fmt, 'd');
}
template<class T>
auto safe_call_helper(auto fn, auto fmt, std::same_as<T> auto arg) {
assert_fmt<T>(*fmt);
fn(arg);
}
template<class T, class U>
auto safe_call_helper(auto fn, auto fmt, T arg_0, U arg_1, auto ... args) {
assert_fmt<T>(*fmt);
fn(arg_0);
safe_call_helper<U>(fn, fmt + 1, arg_1, args...);
}
template<class ... Ts>
using FirstType = std::tuple_element_t<0, std::tuple<Ts...>>;
template<class ... Ts>
auto safe_call(auto fn, auto fmt, Ts ... args) {
if constexpr (sizeof...(Ts)) {
safe_call_helper<FirstType<Ts...>>(fn, fmt, args...);
}
template <auto N>
auto safe_call_impl(auto fn, auto fmt, va_list args, auto... ts) {
if constexpr (N == 0) {
fn(ts...);
} else {
if (*fmt) {
switch (*fmt) {
case 'i':
return safe_call_impl<N - 1>(fn, fmt + 1, args, ts...,
va_arg(args, int));
case 'd':
return safe_call_impl<N - 1>(fn, fmt + 1, args, ts...,
va_arg(args, double));
}
} else {
fn(ts...);
}
}
}
auto safe_call(auto fn, auto fmt, ...) {
std::va_list args{};
va_start(args, fmt);
safe_call_impl<3>(fn, fmt, args);
va_end(args);
auto safe_call(auto fn, std::string_view fmt, ...) -> decltype(auto) {
std::va_list args;
va_start(args, fmt);
using namespace boost::mp11;
return mp_with_index<4>(
std::size(fmt), [&]<auto N>(mp_size_t<N>) -> decltype(auto) {
mp_repeat_c<std::tuple<std::variant<int, double>>, N> t;
mp_for_each<mp_iota_c<N>>([&]<auto I>(mp_size_t<I>) {
switch (fmt[I]) {
case 'i':
std::get<I>(t) = va_arg(args, int);
break;
case 'd':
std::get<I>(t) = va_arg(args, double);
break;
}
});
va_end(args);
return std::apply(
[&](const auto &...vars) -> decltype(auto) {
return std::visit(fn, vars...);
},
t);
});
}
auto safe_call(auto fn, auto fmt, ...) {
std::va_list args;
va_start(args, fmt);
std::stringstream sout;
while(*fmt != '\0') {
if (*fmt=='d')
fn(va_arg(args, double));
if (*fmt =='i')
fn(va_arg(args, int));
fmt++;
}
va_end(args);
}
auto safe_call(auto fn, auto fmt, auto... args){
auto safe_check = [&]<std::size_t... Ns>(std::index_sequence<Ns...>){
auto type_check = [](char type, auto arg){
if( type == 'i'){
return std::is_convertible_v<decltype(arg),int>;
}else if( type == 'd'){
return std::is_convertible_v<decltype(arg),float>;
}
return false;
};
return (true && ... && type_check(fmt[Ns],args));
}(std::make_index_sequence<sizeof...(args)>{});
if(safe_check)
{
return fn(args...);
}
}