一般包装对象的成员函数来修改return类型

Generically wrap member function of object to modify return type

版本限制:C++17.

我正在尝试创建一个类型,该类型能够接受任何类型的可调用对象并包装其成员函数之一(在本例中为 operator())以采用相同的参数,但修改 (cast ) return 类型。示例如下:

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 可以获取一个对象并覆盖该对象的 operator() 以将其 return 转换为 Ret.

问题在于表达包装函数的参数类型 - 它们应该与 Callable::operator() 的参数类型完全相同。使用带有 std::forward 的可变模板不能满足这个目标,因为它会修改函数的签名(operator() 现在变成了以前不是的模板)。

我如何表达上面突出显示的 argsof 运算符?


动机:我想修改 this article 中演示的 std::visit 重载技术,以便能够指定所需的 return来自多个 lambda 仿函数的类型,这样我就不必在每个 lambda 中严格匹配 return 类型,例如:

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

通过上面的更改,我可以写出类似 auto stringify = overload<std::string>(...);

的内容

我看不出有什么方法可以回答您的确切答案,但是...考虑到问题的“动机”...我建议对 overload 进行包装(class从具有一个或多个 operator() 的 class 继承,从基础 class 调用适当的 operator() 并将 return 值转换为类型 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)...); }
};

并且,鉴于 Ret 类型无法从参数(overload class)中推导出来,并且 CTAD 不允许显式模板参数,似乎我需要 make_wrp_overload() 函数

template <typename Ret, typename ... Cs>
auto make_wrp_overload (Cs && ... cs)
{ return wrp_overload<Ret, overload<Cs...>>{{std::forward<Cs>(cs)...}}; }

所以你的 std::visit() 调用变成了

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

下面是一个完整的编译C++17的例子

#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);
}