Home > Enterprise >  Generically wrap member function of object to modify return type
Generically wrap member function of object to modify return type

Time:02-08

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>(...);

CodePudding user response:

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);
}
  •  Tags:  
  • Related