Version restriction: C++17.
I'm trying to create a type capable of accepting a callable object of any type and wrapping one of its member functions (in this case, operator()
) to take the same arguments, but modify (cast) the return type. An example follows:
template <typename Ret, typename Callable>
struct ReturnConverter : Callable
{
ReturnConverter(Callable cb) : Callable(cb) { }
Ret operator()(argsof(Callable::operator()) args) // magic happens here
{
return static_cast<Ret>(Callable::operator()(std::forward<??>(args)); // how to forward?
}
};
template <typename Ret, typename Callable>
auto make_converter(Callable cb)
{
return ReturnConverter<Ret, Callable>(cb);
}
int main()
{
auto callable = []() { return 1.0f; };
auto converted = make_converter<int>(callable);
auto x = converted(); // decltype(x) = int
}
ReturnConverter
can take an object and override that object's operator()
to cast whatever it returns to Ret
.
The problem is expressing the argument types of the wrapping function - they should be exactly the same as those of Callable::operator()
. Using a variadic template with std::forward
does not satisfy this goal, as it would modify the signature of the function (operator()
now becomes a template where it wasn't before).
How can I express the argsof
operator I've highlighted above?
Motivation: I'd like to modify the std::visit
overload technique demonstrated in this article to be able to specify the desired return type from multiple lambda functors, so that I don't have to strictly match the return type in every lambda, for instance:
std::variant<int, float, void*> v = ...;
auto stringify = overload(
[](int x) { return "int: " + std::to_string(x); },
[](float x) { return "float: " + std::to_string(x); },
[](auto v) { return "invalid type!"; } // error! const char* != std::string
);
std::visit(stringify, v);
With the change above, I'd be able to write something like auto stringify = overload<std::string>(...);
I don't see a way to respond to your exact answer but... considering the "Motivation" of the question... I propose a wrapper for overload
(a class that inherit from a class with one or more operator()
, call the appropriate operator()
from the base class and cast the return value to type Ret
)
template <typename Ret, typename Wrpd>
struct wrp_overload : public Wrpd
{
template <typename ... Args>
Ret operator() (Args && ... as)
{ return Wrpd::operator()(std::forward<Args...>(as)...); }
};
and, given the Ret
type isn't deducible from the argument (the overload
class) and that CTAD doesn't permit to explicit a template argumend, seems to me that a make_wrp_overload()
function is required
template <typename Ret, typename ... Cs>
auto make_wrp_overload (Cs && ... cs)
{ return wrp_overload<Ret, overload<Cs...>>{{std::forward<Cs>(cs)...}}; }
so your std::visit()
call become
std::visit(make_wrp_overload<std::string>(
[](int x) { return "int: " + std::to_string(x); },
[](float x) { return "float: " + std::to_string(x); },
[](auto v) { return "invalid type!"; }
), package);
The following is a full compiling C++17 example
#include <iostream>
#include <variant>
template <typename ... Ts>
struct overload : public Ts...
{ using Ts::operator()...; };
// not required anymore (also C++17)
//template <typename ... Ts> overload(Ts...) -> overload<Ts...>;
template <typename Ret, typename Wrpd>
struct wrp_overload : public Wrpd
{
template <typename ... Args>
Ret operator() (Args && ... as)
{ return Wrpd::operator()(std::forward<Args...>(as)...); }
};
template <typename Ret, typename ... Cs>
auto make_wrp_overload (Cs && ... cs)
{ return wrp_overload<Ret, overload<Cs...>>{{std::forward<Cs>(cs)...}}; }
int main() {
std::variant<int, float, void*> package;
std::visit(make_wrp_overload<std::string>(
[](int x) { return "int: " + std::to_string(x); },
[](float x) { return "float: " + std::to_string(x); },
[](auto v) { return "(no more) invalid type"; }
), package);
}