一般包装对象的成员函数来修改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);
}
版本限制: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);
}