C++ 返回std::variant映射中保存的std::函数
我有一个C++ 返回std::variant映射中保存的std::函数,c++,c++17,c++20,C++,C++17,C++20,我有一个std::variant的映射,其中包含几个std::function专门化,例如: //注意不同的返回类型 使用函数\u t=std::variant; std::map回调; 回调[0]=[](){return 9;}; 如何编写一个调用方(…)帮助函数,该函数将为我的变量在索引处保存的映射std::function提供一个引用,从而允许类似于以下内容的调用: int-value=caller(回调,0)(); 由于函数\u t中包含不同的返回类型,因此简单访问者不起作用,即:
std::variant
的映射,其中包含几个std::function
专门化,例如:
//注意不同的返回类型
使用函数\u t=std::variant;
std::map回调;
回调[0]=[](){return 9;};
如何编写一个调用方(…)
帮助函数,该函数将为我的变量在索引处保存的映射std::function
提供一个引用,从而允许类似于以下内容的调用:
int-value=caller(回调,0)();
由于函数\u t
中包含不同的返回类型,因此简单访问者不起作用,即:
// cannot compile
auto caller(std::map<int, function_t> callbacks, int idx) {
return std::visit([](const auto& arg) { return arg; }, callbacks[idx]);
}
//无法编译
自动调用者(std::map回调,intidx){
return std::visit([](const auto&arg){return arg;},回调[idx]);
}
第一部分是仅当参数匹配时才能调用函数:
struct void_t {};
template<class R, class...Args, class...Ts,
// in C++20 do requires
std::enable_if_t<sizeof...(Args)==sizeof...(Ts), bool> = true,
class R0=std::conditional_t< std::is_same_v<R,void>, void_t, R >
>
std::optional<R0> call_me_maybe( std::function<R(Args...)> const& f, Ts&&...ts ) {
if constexpr ( (std::is_convertible_v<Ts&&, Args> && ... ))
{
if constexpr (std::is_same_v<R, void>) {
f(std::forward<Ts>(ts)...);
return void_t{};
} else {
return f(std::forward<Ts>(ts)...);
}
}
else
{
return std::nullopt;
}
}
template<class R, class...Args, class...Ts,
// in C++20 do requires
std::enable_if_t<sizeof...(Args)!=sizeof...(Ts), bool> = true,
class R0=std::conditional_t< std::is_same_v<R,void>, void_t, R >
>
constexpr std::optional<R0> call_me_maybe( std::function<R(Args...)> const& f, Ts&&...ts ) {
return std::nullopt;
}
将会有一些编译器不喜欢我使用的autoi
;关于这些,<代码> DeCype(I)::值<代码>替换<代码> i>代码>可能会帮助(我可以说,不是所有编译器都是C++兼容的)。
基本思想是,我们创建一个变量,其中包含函数可能返回值的匹配索引。然后,我们返回其中的一个可选选项,以处理(在运行时)失败肯定是可能的这一事实
call\u me\u也许
是(除了歌曲参考之外)一种假装我们可以调用任何东西的方式。这就是当R
为void
时,nothing\u t
可能有用的地方
variant\u index\t
是一个技巧,我使用它将变量作为泛型和类型处理,其中可能包含重复类型
首先,我们定义一个编译时整数,称为索引
。它基于现有的std::integral_常量
然后我们做了一个变体,这样备选方案3就是编译时索引3
然后,我们可以使用std::visit([&](auto I){/*…*/},get_variant_index(var))
将变量的索引作为编译时常量使用
如果var
有4个备选方案并保留备选方案2,则get\u variant\u index
返回一个std::variant
,其中填充了index
(在运行时,这似乎是由一个64位整数2
表示的。我觉得这很有趣。)
当我们std::visit
thisvariant\u index
时,我们传递的lambda将传递index\u t
。因此lambda有一个编译时常量传递给它。在非哑编译器中,您可以通过操作符std::size\t
隐式地从索引中提取值。对于哑编译器,必须执行std::decay\u t::value
,这将是相同的编译时整数
使用编译时整数,我们可以std::get(var)
lambda内的值(并保证位于正确位置的值),并且我们可以使用它在同一备选方案中构造另一个变量,即使另一个变量有不明确的备选方案。对你来说,如果你有
std::function<int(int)>
std::function<int(int,int)>
我得到这个输出:
前两行是记录调用的void()
和int(int)
std::function
s
第三行显示了哪些调用成功——对void()
的0参数调用和对int(int)
的1参数调用
最后4行是存储的结果。第一个是可选
,它处于接合状态并保持无效状态。第二次和第三次调用失败,因此nullopt
,最后一次调用包含将1
传递给返回1+1
的函数的结果
从返回值中,您可以查看调用是否有效(查看外部可选项是否已启用),确定调用了哪个回调(如果调用了),并获取被调用变量的值(对其进行访问)
如果函数类型的数量很大,则需要考虑优化。
上面有两个嵌套的变量索引std::visions
,保证返回相同的值。这意味着在只需要O(n)的情况下生成O(n^2)代码,其中n是功能\u t
中的备选方案数
您可以通过将变量索引“down”作为额外参数传递给call\u maybe
和var\u opt\u flip
来清除这个问题。理论上,编译器可以计算出其他n^2-n生成的代码元素是不可访问的,但这两个元素都需要编译器进行大量工作,即使它工作起来也很脆弱
这样做将减少构建时间(这种愚蠢的行为可能会花费构建时间;不要在通常包含的公共头中调用它!),并且可以减少运行时可执行文件的大小
大多数编程语言和C++的大多数用法不允许O(n)代码生成多于O(n)的二进制;但模板功能足够强大,特别是std变体,可以生成O(n^2)甚至O(n^3)二进制代码输出。因此,应该注意一些。
第一部分是仅当参数匹配时才能调用函数:
struct void_t {};
template<class R, class...Args, class...Ts,
// in C++20 do requires
std::enable_if_t<sizeof...(Args)==sizeof...(Ts), bool> = true,
class R0=std::conditional_t< std::is_same_v<R,void>, void_t, R >
>
std::optional<R0> call_me_maybe( std::function<R(Args...)> const& f, Ts&&...ts ) {
if constexpr ( (std::is_convertible_v<Ts&&, Args> && ... ))
{
if constexpr (std::is_same_v<R, void>) {
f(std::forward<Ts>(ts)...);
return void_t{};
} else {
return f(std::forward<Ts>(ts)...);
}
}
else
{
return std::nullopt;
}
}
template<class R, class...Args, class...Ts,
// in C++20 do requires
std::enable_if_t<sizeof...(Args)!=sizeof...(Ts), bool> = true,
class R0=std::conditional_t< std::is_same_v<R,void>, void_t, R >
>
constexpr std::optional<R0> call_me_maybe( std::function<R(Args...)> const& f, Ts&&...ts ) {
return std::nullopt;
}
将会有一些编译器不喜欢我使用的autoi
;关于这些,<代码> DeCype(I)::值<代码>替换<代码> i>代码>可能会帮助(我可以说,不是所有编译器都是C++兼容的)。
基本思想是,我们创建一个变量,其中包含函数可能返回值的匹配索引。然后,我们返回其中的一个可选选项,以处理(在运行时)失败肯定是可能的这一事实
call\u me\u也许
是(除了歌曲参考之外)一种假装我们可以调用任何东西的方式。这就是当使用R
时,nothing\u t
可能有用的地方
template<class...Sigs, class...Ts>
auto call_maybe( std::variant<std::function<Sigs>...> const& vf, Ts&&...ts )
{
using R0 = std::variant< decltype(call_me_maybe(std::function<Sigs>{}, std::forward<Ts>(ts)...))... >;
R0 retval = std::visit(
[&](auto I)->R0 {
return R0( std::in_place_index_t<I>{}, call_me_maybe(std::get<I>(vf), std::forward<Ts>(ts)... ) );
},
get_variant_index(vf)
);
return var_opt_flip( std::move(retval) );
}
using function_t = std::variant< std::function< void() >, std::function< int(int) > >;
template<class...Ts>
auto caller(std::map<int, function_t> const& callbacks, int idx, Ts&&...ts) {
auto it = callbacks.find(idx);
using R = decltype(call_maybe( it->second, std::forward<Ts>(ts)... ));
// wrong index:
if (it == callbacks.end())
return R(std::nullopt);
// ok, give it a try:
return call_maybe( it->second, std::forward<Ts>(ts)... );
}
std::function<int(int)>
std::function<int(int,int)>
std::map<int, function_t> callbacks = {
{ 0, []{ std::cout << 0 << "\n"; } },
{ 1, [](int x){ std::cout << "1:" << x << "\n"; return x+1; } },
};
std::optional<std::variant<void_t, int>> results[] = {
caller(callbacks, 0),
caller(callbacks, 0, 1),
caller(callbacks, 1),
caller(callbacks, 1, 1),
};
for (auto&& op:results) {
std::cout << (bool)op;
}
std::cout << "\n";
auto printer = [](auto val) {
if constexpr (std::is_same_v<decltype(val), void_t>) {
std::cout << "void_t";
} else {
std::cout << val;
}
};
int count = 0;
for (auto&& op:results) {
std::cout << count << ":";
if (!op) {
std::cout << "nullopt\n";
} else {
std::visit( printer, *op );
std::cout << "\n";
}
++count;
}
0
1:1
1001
0:void_t
1:nullopt
2:nullopt
3:2