返回的std :: function保留在std :: variant的映射中

蓬松

我有一个包含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]);    
}
Yakk-亚当·内夫罗蒙特

第一部分是仅当参数匹配时才可以调用函数:

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...>>

现在,我们需要能够建立正确的返回值。

现在我们可以编写一个函数,它采用函数和参数的变体,并且可能调用活动函数:

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_tRis,a可能会有用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,这将是相同的编译时整数。

使用编译时整数,我们可以std::get<I>(var)在lambda内进行值设置(并保证在正确的位置),并且即使该其他变体具有不明确的选择,我们也可以使用它来构造另一个变体。就您而言,您会发现

std::function<int(int)>
std::function<int(int,int)>

“结果变量”看起来像std::variant<int,int>-与有所不同std::variant<int>

(作为一个额外的步骤,您可以从此变体中删除重复的类型,但我建议您单独进行此操作)

每个call_me_maybe调用都返回一个optional<R>但是avariant<optional<R>...>愚蠢的,所以我将其翻转为a optional<variant<R>...>

这意味着您可以快速检查函数调用是否有效,如果可以,则可以看到从中获得了什么价值。


测试代码:

    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记录他们的呼叫。

第三行显示成功的调用-对的0参数调用void()和对的1参数调用int(int)

最后4行是存储的结果。第一个optional<variant>参与并持有void_t这样第二次和第三次调用失败了nullopt,最后一个调用包含传递1给返回的函数的结果1+1

现场例子

从返回值中,您可以查看该调用是否有效(查看是否使用了外部可选函数),确定调用了哪个回调(如果是)(变量索引),并获取被调用变量的值(对其进行访问) )。


如果函数类型的数量很大,则应考虑进行优化。

上面有两个嵌套std::visits的变量索引,保证都返回相同的值。这意味着仅在需要O(n)的情况下会生成O(n ^ 2)代码,其中n是中的替代数function_t

您可以通过将变体索引“向下”传递给call_maybevar_opt_flip作为额外的参数来清理从理论上讲,编译器可以得出其他n ^ 2-n生成的代码元素不可访问的信息,但是这两者都需要编译器方面的大量工作,并且即使工作也很脆弱。

这样做将减少构建时间(并且这种假冒行为可能会花费构建时间;请勿在通常包含的公共头文件中调用它!),并可能减小运行时可执行文件的大小。

大多数编程语言和C ++的大多数用法都不允许O(n)代码生成比O(n)二进制代码更多的代码。但是模板足够强大,尤其是std变体,可以生成O(n ^ 2)甚至O(n ^ 3)二进制代码输出。因此,应注意一些事项。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

尝试将值存储到映射中的std :: variant时,std :: bad_variant_access错误

在 C++ 中包含 `std::variant` 的映射中填充映射的最简单方法是什么?

遍历std :: variant的映射

从函数返回std :: variant

返回类型std :: optional <std :: variant <... >>

std :: hash用于无序映射中的唯一ptr

带有 std::pair 的映射中的 C++ 计数函数

当请求的键丢失时,有没有办法从向量映射中返回 std::vector ref ?

从std :: variant到std :: visit的可能类型返回值

初始化std :: variant的映射以表示JSON文件

如何在映射中获取std :: set中给定下限和上限的值范围?

尝试使用std :: visit和lambda表达式从std :: variant返回值

如何访问 kubectl 返回的映射中的密钥

从映射中的回调返回值

创建std :: function,它返回带有函数成员值的variant。分段故障

std :: bind到包含多种std :: function类型的std :: variant

确定std :: function的返回类型

不复制就返回std :: function

可以使用 std::variant 的 std::variant

从映射中返回与返回值类型匹配的值

从std :: copy返回的OutputIterator

std :: stringstream返回char *

定义一个函数以返回指定类型的std :: variant C ++

如何返回在 std::variant 的所有替代项中定义的成员,即使成员是不同的类型?

返回对std :: variant的引用,该引用包含不可复制的替代项

pyspark将更改保留在映射器范围之外

JavaScript数组映射,同时将原始元素保留在数组中

双向映射空值保留在子级的父级引用列中

Vim NERDTree | 将'<cr>'重新映射为'go'| 打开文件,但保留在NerdTree窗口中