我有一张std::variant
映射,其中包含几个std::function
专长,例如:
// note the different return types
using function_t = std::variant<std::function<int(void)>, std::function<void(int)>>;
std::map<int, function_t> callbacks;
callbacks[0] = [](){ return 9; };
如何编写caller(...)
辅助函数,该函数将为我提供在变体中索引处映射的std::function
的引用,从而允许进行类似于以下的调用:int value = caller(callbacks, 0)();
由于function_t
中保存的返回类型不同,因此简单的访客无法使用,即:// cannot compile
auto caller(std::map<int, function_t> callbacks, int idx) {
return std::visit([](const auto& arg) { return arg; }, callbacks[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;
}
第二部分涉及变体的一些工作:template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index = {};
template<std::size_t...Is>
using variant_index_t = std::variant< index_t<Is>... >;
template<std::size_t...Is, class R=variant_index_t<Is...>>
constexpr R make_variant_index( std::size_t I, std::index_sequence<Is...> ) {
constexpr R retvals[] = {
R( index<Is> )...
};
return retvals[I];
}
template<std::size_t N>
constexpr auto make_variant_index( std::size_t I ) {
return make_variant_index( I, std::make_index_sequence<N>{} );
}
template<class...Ts>
constexpr auto get_variant_index( std::variant<Ts...> const& v ) {
return make_variant_index<sizeof...(Ts)>( v.index() );
}
这样一来,您就可以以更易于编译的方式使用变量索引。template<class...Ts>
std::optional<std::variant<Ts...>> var_opt_flip( std::variant<std::optional<Ts>...> const& var ) {
return std::visit( [&](auto I)->std::optional<std::variant<Ts...>> {
if (std::get<I>(var))
return std::variant<Ts...>(std::in_place_index_t<I>{}, *std::get<I>(var));
else
return std::nullopt;
}, get_variant_index(var) );
}
这使我们可以使用variant<optional<Ts>...>
并生成optional<variant<Ts...>>
,即使存在重复的类型。现在,我们需要能够建立正确的返回值。
现在,我们可以编写一个函数,它采用函数和参数的变体,并且可能调用 Activity 函数:
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) );
}
然后我们重写caller
以使用它: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)... );
}
将会有一些编译器不喜欢我对auto I
所做的事情;在这些上,用decltype(I)::value
代替I
可能会有所帮助(我可以说,并非所有编译器都符合C++)。基本思想是我们创建一个具有匹配索引的变量,这些变量可能具有函数的返回值。然后,我们返回其中的一个可选值,以处理在运行时肯定有可能发生故障的事实。
call_me_maybe
是(超越歌曲引用)一种能够假装我们可以调用任何东西的方式。当nothing_t
是R
时,这就是void
可能有用的地方。variant_index_t
是我用来将变量作为通用和类型(其中可能包含重复类型)来处理的一个技巧。首先,我们定义一个称为
index
的编译时间整数。它基于现有的std::integral_constant
。然后,我们对它们进行变型,以使替代项3为编译时索引3。
然后,我们可以使用
std::visit( [&](auto I){/*...*/}, get_variant_index(var) )
将变体的索引用作编译时间常数。如果
var
具有4个替代方案并保留替代方案2,则get_variant_index
返回其中填充了std::variant<index<0>, index<1>, index<2>, index<3>>
的index<2>
。(在运行时,这很有可能由一个64位整数
2
表示。我觉得这很有趣。)当我们对此
std::visit
进行variant_index
编码时,我们通过传递的lambda会通过index_t<I>
传递。因此,lambda传递了一个编译时间常数。在不笨的编译器中,您可以constexpr
通过隐式的index_t<I>
从operator std::size_t
中提取值。对于笨拙的编译器,您必须执行std::decay_t<decltype(I)>::value
,它将是相同的编译时整数。使用的编译时整数,我们可以对lambda内的值进行
std::get<I>(var)
(并保证在正确的位置),并且可以使用它在相同的替代项上构造另一个替代项,即使该替代项具有歧义。就您而言,您会发现std::function<int(int)>
std::function<int(int,int)>
“结果变量”看起来像std::variant<int,int>
,这与std::variant<int>
不同。(作为一个额外的步骤,您可以从此变体中删除重复的类型,但我建议您单独进行此操作)
每个
call_me_maybe
调用都返回一个optional<R>
。但是variant<optional<R>...>
是 dumb ,因此我将其翻转为optional<variant<R>...>
。这意味着您可以快速检查函数调用是否有效,如果可以,则可以看到从中获得了什么值(value)。
测试代码:
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
前两行是
void()
和int(int)
std::function
,记录他们的 call 。第三行显示成功的调用-调用
void()
的0参数和调用int(int)
的1参数。最后4行是存储的结果。第一个启用了
optional<variant>
并保留了void_t
。第2次和第3次调用失败,因此为nullopt
,最后一个调用包含将1
传递给返回1+1
的函数的结果。Live example。
从返回值中,您可以查看该调用是否有效(查看是否使用了外部可选函数),确定调用了哪个回调(如果是)(变量索引),并获取被调用变量的值(对其进行访问) )。
如果函数类型的数量很大,则应考虑进行优化。
上面有两个嵌套的varit索引的
std::visits
,都保证返回相同的值。这意味着仅在需要O(n)的情况下会生成O(n ^ 2)代码,其中n是function_t
中的替代项数。您可以通过将变体索引“向下”传递给
call_maybe
和var_opt_flip
作为额外的参数来进行清理。从理论上讲,编译器可以得出其他n ^ 2-n生成的代码元素不可访问的信息,但是这两者都需要编译器方面的大量工作,并且即使工作也很脆弱。这样做将减少构建时间(并且这种假冒行为可能会花费构建时间;请勿在通常包含的公共(public)头文件中调用它!),并可能减小运行时可执行文件的大小。
大多数编程语言和C++的大多数用法都不允许O(n)代码生成比O(n)二进制代码更多的代码。但是模板足够强大,尤其是std变体,可以生成O(n ^ 2)甚至O(n ^ 3)二进制代码输出。因此,应注意一些事项。
关于c++ - 返回的std::function保留在std::variant的映射中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64337532/