I need to convert variant<type1, type2, ...>
to another variant<ftype1, ftype2, ...>
where ftypeN
is the return type of function fn()
invoked with parameter of typeN
, subject to the following conditions:
fn()
is not invocable on typeN
the type of ftypeN
is wrong_arg_type
fn()
returns void
, the type of ftypeN
is void_type
ftypeN
is the return type of fn(typeN)
The resultant variant
should have the same index as input variant
and value equal to fn(get<I>(v))
or default initialized void_type
or wrong_arg_type
. Function fn()
should be called at most once.
This should work with three current C++23 compilers: gcc/clang/MSVC
This is my current implementation of return type deduction:
struct void_type{};
struct wrong_arg_type{};
template<class Fn, class...Args>
using ret_t = std::conditional_t<std::is_invocable_v<Fn, Args...>,
std::conditional_t<std::is_void_v<std::invoke_result_t<Fn, Args...>>,
void_type,
std::invoke_result_t<Fn, Args...>>,
wrong_arg_type
>;
template<class Fn, class... Args>
constexpr auto invoke_fn(Fn fn, Args&&...args)
{
if constexpr(std::is_same_v<ret_t<Fn, Args...>, void_type>)
return std::invoke(std::forward<Fn>(fn), std::forward<Args>(args)...),
void_type{};
else if constexpr(std::is_same_v<ret_t<Fn, Args...>, wrong_arg_type>)
return wrong_arg_type{};
else
return std::invoke(fn, std::forward<Args>(args)...);
}
And my current implementation of transform
function:
template<class Fn, class...T>
auto transform(Fn fn, const std::variant<T...>& v)
{
using variant_type = std::variant<ret_t<Fn, T>...>;
return [&]<auto N>(this auto&& self, std::size_t idx, std::index_sequence<N>)
-> variant_type
{
if constexpr(N == sizeof...(T))
{ throw "transform error"; }
else if (N == idx)
return variant_type(std::in_place_index<N>, invoke_fn(fn, std::get<N>(v)));
else
return self(idx, std::index_sequence<N + 1>{});
}(v.index(), std::index_sequence<0>{});
}
Compiler explorer demo is here.
There are two problems with this code I currently can't figure out.
First, the case when function is not invocable fails to compile (std::tuple
in the above demo).
<source>:15:29: error: no type named 'type' in 'struct std::invoke_result<overloaded<main()::<lambda(double)>, main()::<lambda(int)>, main()::<lambda(const std::string&)> >, std::tuple<int, int> >'
15 | std::conditional_t<std::is_void_v<std::invoke_result_t<Fn, Args...>>,
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Second, MSVC gives some cryptic error even when the function is invocable.
<source>(44): error C2231: '.fn': left operand points to 'class', use '->'
Could you suggest a fix for those issues?
I found a solution to the problem of std::conditional
by changing the implementation of invoke_fn
, due to the fact that only one if constexpr
branch being evaluated, and ret_t
is just a decltype
of it.
template<class Fn, class... Args>
constexpr auto invoke_fn(Fn fn, Args&&...args)
{
if constexpr(not std::is_invocable_v<Fn, Args...>)
return wrong_arg_type{};
else if constexpr(std::is_void_v<std::invoke_result_t<Fn, Args...>>)
return std::invoke(std::forward<Fn>(fn), std::forward<Args>(args)...),
void_type{};
else
return std::invoke(fn, std::forward<Args>(args)...);
}
template<class Fn, class... Args>
using ret_t = decltype(invoke_fn(std::declval<Fn>(), std::declval<Args>()...));
The second problem is related to MSVC buggy implementation of recursive lambda. Replacing it with a template function solves this problem.
template<auto N, class Fn, class...T>
auto transform_helper(Fn fn, const std::variant<T...>& v)
->std::variant<ret_t<Fn, T>...>
{
using variant_type = std::variant<ret_t<Fn, T>...>;
if constexpr(N == sizeof...(T))
{ throw "transform error"; }
else if (N == v.index())
return variant_type(std::in_place_index<N>, invoke_fn(fn, std::get<N>(v)));
else
return transform_helper<N+1>(fn, v);
}
template<class Fn, class...T>
auto transform(Fn fn, const std::variant<T...>& v)
{
return transform_helper<0>(fn, v);
}
Here is a working demo