Reputation: 325
Here's the lambda overload based visitor given in https://en.cppreference.com/w/cpp/utility/variant/visit:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
std::visit(overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}, v);
where v
is a variant
and all its types must have implemented lambdas.
I want to add a function to overloaded
that has the same return type as all lambdas (which is then the return type of visit(overloaded{...}, v)
).
Something similar to:
template<class... Ts> struct overloaded : Ts...
{
using Ts::operator()...;
common_type_t<decltype(declval<Ts>())...> test() const { return {}; }
};
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
auto r = std::visit(overloaded {
[](auto arg) { return 1; },
[](double arg) { return 2; },
[](const std::string& arg) { return 3; },
}, v);
Here something goes wrong and common_type<_Tp...>::type
doesn't exist.
I thought about using common_type_t<invoke_result_t<Ts>...>
but I don't have the lambdas' arguments, so invoke_result<Ts..>>::type
fails there.
What am I missing?
Upvotes: 2
Views: 888
Reputation: 26342
This problem has no general solution, because if one of the lambdas is generic, e.g.
[](auto arg) { return arg; }
then there is no way to deduce the return type without std::variant
object.
However, we can try to deduce common type from those lambdas that have a non-template operator()
. The solution for this special case follows.
First make a trait that deduces the return type of a lambda, or returns empty
if deduction fails:
struct empty {};
template<typename Ret, typename Fn, typename Arg>
Ret return_type(Ret(Fn::*)(Arg));
template<typename Ret, typename Fn, typename Arg>
Ret return_type(Ret(Fn::*)(Arg) const);
template<typename Fn>
auto return_type(Fn) -> decltype(return_type(&Fn::operator()));
empty return_type(...);
Then tweak std::common_type
:
template<>
struct std::common_type<empty, empty> {
using type = empty;
};
template<typename T>
struct std::common_type<T, empty> {
using type = T;
};
template<typename T>
struct std::common_type<empty, T> {
using type = T;
};
And finally put all this together:
template<class... Ts> struct overloaded : Ts... {
using Ts::operator()...;
static auto test() ->
std::common_type_t<decltype(return_type(std::declval<Ts>()))...>;
};
Example:
auto fn = overloaded {
[](auto arg) { },
[](int arg) { return 0; },
[](std::string) { return 1.; }
};
static_assert(std::is_same_v<decltype(fn.test()), double>);
If no type can be deduced, the return type of test()
will be empty
:
auto fn = overloaded {
[](auto arg) { }
};
static_assert(std::is_same_v<decltype(fn.test()), empty>);
Upvotes: 2