6

C++17 中的 std::any 等等

 3 years ago
source link: https://hedzr.github.io/c++/variant/any-in-c++17/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

不直入主题Permalink

本篇之中,仅仅述及 std::any,也暂时是这批和 variant 相关的话题的最后一篇了。

但开篇之前,要分享一个经验:

如果使用 ruby 环境例如挂着 jekyll 服务器在本地写 gh-pages,然后去 Finder 中复制一篇旧 Post 副本,点它,你想的是要改名做一篇新 Post 对吗,但 big sur 会死,死了之后会重启。

我 #%^@*#。

所以首先我花费一小节去研究 ruby 要不要升级,吗?

终端窗口中看得到,当我 duplicate 一篇文章时,Jekyll 正在卖力地 generating htmls,此时 rename in finder 总是死,这几天凌晨写 posts,今天遇到第二次,所以确定这个路数,我吃了这包药了。可惜的是,这特么连搜索解决方案都不可能,根本无法构造一个问问题的句子。

它看起来不像是真的 ruby 或者 Jekyll 的问题,然而我这里几乎没有 fuse 类的驱动了,NTFS driver 也都清理得干干净净了,只剩下 iCloud 了。

C++17 之前Permalink

std::any 有点像 Nullable Variant,在早前似乎没有严格的对应物。不过,早期的多种 variant 实现都支持 NULL 指针对象的放入,所以 std::any 也可以与它们勉强适配以资对照。

std::any in C++17Permalink

由于早前两篇文章介绍 std::variantstd::optional 时已经提前做了不少的比较,所以本篇之中再来提及 std::any 时,也没有太多稀奇的东西了。

首先你已经知道,std::any 是一个确切的变体类型,这意味着在其变量生存周期中,你可以随时放入不同数据类型的值,可以先放入 string,然后在放入 float,没有问题。

而且在这一点上,它比 std::variant 更加放荡。std::variant 说,咱们提前说好了,只有黄金裘的人儿可以进来,然后你就只能放入黄金裘的值。但 std::any 说的是,我不管,我不管,谁都可以进来。所以无需特别声明一个允许的数据类型列表,你可以直接使用 std::any,而且放入的数据类型没有限制。

当然,尽管这么说,你还是不应放入引用类型。

使用Permalink

用法下面做一些小小的示例。

包含头文件

#include <any>
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
a = 3.14;
std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
a = true;
std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';
// 有误的转型
try
{
  a = 1;
  std::cout << std::any_cast<float>(a) << '\n';
}
catch (const std::bad_any_cast& e)
{
  std::cout << e.what() << '\n';
}

测试有无有效值

a = 1;
if (a.has_value())
{
  std::cout << a.type().name() << '\n';
}

// 重置
a.reset();
if (!a.has_value())
{
  std::cout << "no value\n";
}

可以对 std::any 拥有的值做取地址的操作,该指针的可靠的:

// 指向所含数据的指针
a = 1;
int* i = std::any_cast<int>(&a);
std::cout << *i << "\n";

增强的构造Permalink

std::any 同样也有原位构造以及赋值 emplace,支持 swap,也支持 any 之间的复制。

使用 make_any

auto a0 = std::make_any<std::string>("Hello, std::any!\n");

typeid 和观察器Permalink

std::any 有一个特别的 type() 函数能够返回所包含值的 typeid。

所以可以有几种方式来尝试从 any 中抽出值:

static void ingest_any(const std::any &any) {
    try {
        std::cout << std::any_cast<std::string>(any) << "\n";
    } catch (std::bad_any_cast const &) {}

    if (std::string *str = std::any_cast<std::string>(&any)) {
        std::cout << *str << "\n";
    }

    if (std::type_index{typeid(std::string)} == any.type()) {
        //  Known not to throw, as previously checked.
        std::cout << std::any_cast<std::string>(any) << "\n";
    }
}

除此而外,visitor 模式也可以,但需要稍稍有所动作,因为你需要自行编写 visitor 工具:

#include <any>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>

template<class Visitor>
  void visit_any_as(std::any const &, Visitor &&) {
  throw std::logic_error("std::any contained no suitable type, unable to visit");
}

template<class First, class... Rest, class Visitor>
  void visit_any_as(std::any const &any, Visitor &&visitor) {
  First const *value = std::any_cast<First>(&any);
  if (value) {
    visitor(*value);
  } else {
    visit_any_as<Rest...>(any, std::forward<Visitor>(visitor));
  }
}

int main(){
  std::any any{-1LL};
  try {
    visit_any_as<std::string, int, double, char>(any, [](auto const &x) {
      std::cout << x << std::endl;
    });
  }
  catch (std::exception const &e) {
    std::cout << e.what() << std::endl;
  }
}

这是来自于 Roman Odaisky 的网络回答 中的样例,非常精炼,所以直接取用了(略有调整以便能跑)。

但这个 visitor 要求你必须提供已知的类型的具体版本,所以使用它需要研究具体场合。有的时候可能还是直接 any_cast 来得简便,只不过代码看起来有点恶形而已——特别是连续处理几种数据类型时。

小小结Permalink

std::any 是一个有力的类模板,然而你要想借助它来操作变体类型的话,手脚还是有点麻烦的,主要问题在于取用时需要一些冗长的编码才行。

小结Permalink

这一次,最近几篇文章中,我们已经规规矩矩地回顾了 C++17 中的新模板工具类:std::variantstd::optionalstd::any

它们的特点或者说区别也是很明显的:

  • variant 需要声明一组可用的数据类型,你可以在这组类型的范围之中来使用变体类型
  • any 允许你自由地使用变体类型
  • optional 是一个现代 C++ 版的 Nullable<T> 工具

然而,它们都不能确切地满足 cmdr 的需求。

Permalink


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK