1

版本限制:C++17。

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

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()以将它返回的任何内容转换为Ret.

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

我如何表达argsof我上面强调的运算符?


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

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

4

1 回答 1

2

我看不到回应您确切答案的方法,但是...考虑到问题的“动机”...我建议为overload(从具有一个或多个的类继承的类的包装器operator(),调用适当operator()的基类并将返回值转换为类型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类)中推导出来,并且 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);
}
于 2022-02-06T18:26:01.840 回答